Анализ PL/SQL кода в Oracle 11g (Перевод "Zoom In on Your Code" By Steven Feuerstein)
на
19:31
0
коммент.
Ярлыки:
перевод,
oracle,
PL/Scope,
pl/sql,
translation
С момента первого релиза PL/SQL, Oracle предоставляет набор представлений позволяющих разработчику получать информацию о PL/SQL объектах. Эти представления помогают нам понимать и анализировать наш код. Oracle Database 11g предоставляет еще более мощный аналитический инструмент - PL/Scope, собирающий информацию обо всех идентификаторах в вашем PL/SQL коде, которая затем доступна через представления словаря данных. Эти представления могут помочь отследить использование каждой переменной в определении, ссылках, вызовах, а также местоположение каждого вхождения переменной в исходном коде.
Благодаря PL/Scope разработчики могут значительно лучше и легче выполнять анализ кода. Некоторые полезные приемы его использования и будут рассмотрены в данной статье.
"Лишние" типы
на
20:34
0
коммент.
Ярлыки:
коллекции,
типы,
collection,
oracle,
pl/sql,
predefined типы
CREATE OR REPLACE TYPE sys.ku$_ObjNumPairList IS TABLE OF ku$_ObjNumPair CREATE OR REPLACE TYPE sys.ku$_ObjNumPair AS OBJECT ( num1 NUMBER, num2 NUMBER )
DB11G> select *
2 from table(
3 select
4 cast(
5 collect(ku$_objnumpair(rownum,rownum))
6 as ku$_objnumpairlist
7 )
8 from dual
9 connect by rownum<=10
10 );
NUM1 NUM2
---------- ----------
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
10 rows selected.
Elapsed: 00:00:00.02
Ref cursor в коллекцию на лету
на
19:44
1 коммент.
Ярлыки:
oracle,
pl/sql,
sys_refcursor,
sys.anydata
Господа, подскажите, есть ли способ преобразовать реф.курсор в коллекцию 'налету' в селекте?В принципе задача идиотская, конечно, но захотелось побаловаться, поэтому и "набредил" извращение с коллекцией sys.anydata. А вообще, если иметь ввиду не только простые селекты объектов, а вообще на любые запросы, то их не обернуть так просто в anydata не заводя своего типа...
Т.е есть скажем объектный тип MY_TYPE_1, коллекция MY_TYPE_COLL_1 IS TABLE OF MY_TYPE1
и курсор:
OPEN CURS FOR
SELECT MY_TYPE_COLL_1( MY_TYPE_1(1), MY_TYPE_1(2) ) FROM DUAL;
Хотелось бы в селекте преобразовать этот курсор в коллекцию, сейчас делаю это с помощью ф-ии
FUNCTION MakeColl_1(ACurs SYS_REFCURSOR) return MY_TYPE_COLL_1 IS
Res MY_TYPE_COLL_1;
BEGIN
FETCH ACurs BULK COLLECT INTO Res;
RETURN Res;
END;
Т.к как типов MY_TYPE_COLL_x - много, хотелось бы не городить на каждый по однотипной ф-ии,
а преобразовать прямо в селекте, либо обойтись одной универсальной ф-ией для всех типов курсоров.
Есть мысли, как?
В общем, вот:
create or replace type anydata_table as table of sys.anydata;
CREATE OR REPLACE TYPE MY_TYPE IS OBJECT (
field1 VARCHAR2(100),
field2 VARCHAR2(100),
field3 VARCHAR2(100))
/
create or replace function sys_refcursor_to_anydata_table(cur in sys_refcursor)
return anydata_table
is
l_anydata_table anydata_table;
begin
fetch cur bulk collect into l_anydata_table;
return l_anydata_table;
end sys_refcursor_to_anydata_table;
/
declare
l_cursor sys_refcursor;
l_table anydata_table;
l_my_type my_type;
begin
open l_cursor for
'select
sys.anydata.ConvertObject(my_type(level,level*2,level*3))
from dual
connect by level <=10';
l_table:=sys_refcursor_to_anydata_table(l_cursor);
for i in l_table.first..l_table.last
loop
if l_table(i).getObject(l_my_type) = dbms_types.SUCCESS then
case l_table(i).gettypename()
when user||'.MY_TYPE'
then
dbms_output.put_line( l_table(i).gettypename()||'('
||l_my_type.field1||','
||l_my_type.field2||','
||l_my_type.field3||')'
);
else
dbms_output.put_line('Unexpected type:'||l_table(i).gettypename());
end case;
end if;
end loop;
close l_cursor;
end;
Вот что, оказывается, финалисты получают :)
Своего рода утешительный приз за 29-е место из 34 финалистов :(
PS. Все-таки перфекционизм - зло, одни расстройства из-за него...
Вообще грустно, что нет красивого и удобного решения, применимые варианты скудны, если хочется создать нормальный тип-перечисление с возможностью использования вроде:
declare
var my_typelist;
begin
var:= my_typelist.value1;
end;
все что более менее приближенно к этому:
1. создание пакета типа my_typelist_values с константами - тогда можно использовать так:
declare
var my_typelist;
begin
var:= my_typelist_values.value1;
end;
2. создание типа и объекта - аналог пакета с константами
3. создание объекта my_typelist:
create or replace type my_typelist as object (
value integer,
static function value1 return my_typelist,
static function value2 return my_typelist
)
/
create or replace type body my_typelist as
static function value1 return my_typelist as
begin
return my_typelist(1);
end;
static function value2 return my_typelist as
begin
return my_typelist(2);
end;
end;
/
и использовать:
declare
var my_typelist;
begin
var:=my_typelist.value1;
dbms_output.put_line(var.value);
end;
В общем, жаль... Остается надеяться, что в будущем oracle все-таки сделает нормальные enum'ы...
На очередное коротенькое баловство вдохновил этот вопрос
Скрипт обновлен, т.к. предыдущий был не оптимальным. Данный вариант лучший среди вариантов с решениями с моделью:
select a, max(b) from ( select a,b from t model return all rows dimension by (row_number() over(order by a,b) rn) measures(a, greatest(b, nvl(max(b) over(order by a asc,b desc rows between unbounded preceding and 1 preceding),b) ) b) rules ( a[any]= case when a[cv()] <= b[cv()-1]+1 then a[cv()-1] else a[cv()] end ) ) group by a;
А теперь наиболее быстрый вариант - вариант на аналитике:
select * from ( select nvl(sg,lag(sg) over(order by nvl(sg,eg))) sg, eg from ( select a, b, case when a-1>max(b) over(order by a asc,b desc rows between unbounded preceding and 1 preceding) or max(b) over(order by a asc,b desc rows between unbounded preceding and 1 preceding) is null then a end sg, case when ( lead(a) over(order by a asc,b desc) > 1+max(b) over(order by a asc,b desc rows between unbounded preceding and current row) ) or lead(a) over(order by a asc,b desc) is null then max(b) over(order by a asc,b desc rows between unbounded preceding and current row) end eg from t ) where sg is not null or eg is not null )where eg is not null
Пакет для выполнения команд операционной системы в Oracle с таймаутом, пакет для экспорта исходных кодов функций, процедур, пакетов и тд и работы с SVN, GIT etc
На девелоперской базе очень удобно выгружать весь код и сравнивать его с текущей веткой в системе управления версиями, будь то хоть subversion, git или mercurial. Например, тимлид может каждую пятницу смотреть, что забыли закоммитить и вовремя напомнить программерам описать и закоммитить изменения. К тому же можно там же автоматически сгенерировать документацию по ним для данной ветки с помощью doxygen и положить на девелоперский вебсервер.
Это очень легко реализуется прямо на девелоперской базе выгрузкой кодов в соответствующие директории на сервере и выполнением svn или git status. Для выполнения же команд на сервере есть множество различных функций, но нигде я не нашел функций с возможностью установки таймаута выполнения команды, поэтому написал свою.
Функционала пока маловато, т.к. мало времени и был бы рад, если кто-нибудь присоединился к работе над пакетом, но тем не менее прошу любить и жаловать :)- Пакет для выполнения команд в оси: http://github.com/xtender/xt_shell
- Пакет для выгрузки кода и зачатков работы с svn: http://github.com/xtender/xt_svn
Пакет XT_SHELL
Функция xt_shell.shell_exec(pCommand in varchar2,timeout in number)
Возвращает коллекцию varchar2 (varchar2_table)
Параметры:- pCommand - строка команды
- timeout - таймаут в миллисекундах, после которого выполнение команды прервется
Установка
Для установки выполняем скрипты в следующем порядке:
- @varchar2_table.tps
- @xt_shell.jsp
- @XT_SHELL.spc
Примеры:
declare
output varchar2_table;
begin
output:=xt_shell.shell_exec('/bin/ls -l',100);
for c in output.first..output.last loop
dbms_output.put_line(output(c));
end loop;
end;
select * from table(xt_shell.shell_exec('/bin/ls -l',100))
Пакет XT_SVN
Переменные по умолчанию, которые вы можете переопределить
- REP_PATH varchar2(30):='/var/svn';
- SVN_PATH varchar2(30):='/usr/bin/svn';
Функции:
function export( pPath varchar2 default REP_PATH, pOwner varchar2 default user, pType varchar2 default '%', pName varchar2 default '%') return number;
Выгружает исходные коды соответствующие маскам pOwner,pType,pName в pPath директорию и возвращает количество выгруженных скриптов.
function svn_status(pPath varchar2 default REP_PATH) return varchar2_table;
Возвращает svn status директории pPath в виде коллекции varchar2(varchar2_table)
function svn_statuses(pPath varchar2 default REP_PATH) return svn_status_table pipelined;
Тоже самое, но возвращает в виде коллекции объектов
function svn_checkout( pUrl varchar2, pPath varchar2 default REP_PATH, pUser varchar2 default null, pPass varchar2 default null) return varchar2_table;
Выполняет checkout из svn
function svn_commit( pPath varchar2 default REP_PATH, pUser varchar2 default null, pPass varchar2 default null ) return varchar2_table;
Коммитит изменения в pPath
function svn( command varchar2) return varchar2_table;
Выполняет команду "svn "||command
Примеры:
declare output varchar2_table; begin output:=xt_svn.svn_status(); for c in output.first..output.last loop dbms_output.put_line(output(c)); end loop; end;
select * from table(xt_svn.svn_statuses('/var/svn/project1'))
Установка
- Устанавливаем http://github.com/xtender/xt_shell
- Выполняем скрипты в следующем порядке:
- @svn_types.sql
- @xt_svn.jsp
- @XT_SVN.pck
- Даем гранты на чтение/запись выполнение - примеры в grants.sql
Шел дождь. Иванов оглянулся и произнес: "Я пойду за зонтом. Выходи через полчаса."
В Oracle функции регулярных выражений слишком малофункциональны, что достаточно легко обходится с помощью хранимок на java - в java регулярные выражения значительнее функциональнее. Например, в оракловых регулярках нет look-around assertion, а в java - есть. Это и сподвигнуло меня написать пакет, который выполняет данные операции в java stored функциях.
UPD: Внес кое-какие изменения в текст пакетов, актуальные версии выложил на GitHub.UPD2: Добавил несколько вариантов разбиения строки на подстроки с помощью разделителя
Что этот пакет умеет:
function split_simple(p_str in varchar2,p_delim in varchar2)
return varchar2_table pipelined;
Это PL/SQL-ная функция, разбивающая не по регулярному выражению, а просто по заданной подстроке. Введена просто для удобства.
Данная функция разбивает по заданному регулярному выражению, где выражение и является разделителем.Первый параметр строка для разбиения, второй - паттерн регулярного выражения, третий - необязательный - максимальное количество частей, на которые нужно разбить.function split(pStr varchar2,pDelimRegexp varchar2,pMaxCount number default 0)
return varchar2_table;
Пример:
select * from table(
xt_regexp.split('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=,)\s')
);
В примере разбивается по пробелам, перед которыми есть запятая.
Еще пример:
Разбиваем по пробелам с максимумом в 3 частиselect * from table(
xt_regexp.split('Пожалуйста, сьешьте еще мягких французских булочек!','\s',3)
);

function matches(pStr varchar2,pPattern varchar2,
pCANON_EQ number default 0,
pCASE_INSENSITIVE number default 0,
pCOMMENTS number default 0,
pDOTALL number default 0,
pMULTILINE number default 0,
pUNICODE_CAS number default 0,
pUNIX_LINES number default 0
)
return boolean;
Данная функция возвращает true, если в строке находится подстрока удовлетворяющая регулярному выражению. Первый параметр - строка, вторая - регулярка, остальные параметры - это модификаторы рег.выражения:
- CANON_EQ - каноническое равенство;
- CASE_INSENSITIVE - игнорирование регистра символов;
- COMMENTS - режим игнорирования пробелов с разрешением однострочных комментариев в шаблоне с символа #;
- DOTALL - подразумевать ли под точкой символ новой строки (по умолчанию нет);
- LITERAL - представлять шаблон как обычную строку, т.е. игнорировать значения спец. символов регулярных выражений как +;
- MULTILINE - многострочный режим;
- UNICODE_CASE - регстр букв с учетом юникода;
- UNIX_LINES - режим юниксовых строк, т.е. разделителем строк является только символ Rn
function get_matches(
pStr varchar2,
pPattern varchar2,
pMaxCount number default 0,
pCANON_EQ number default 0,
pCASE_INSENSITIVE number default 0,
pCOMMENTS number default 0,
pDOTALL number default 0,
pMULTILINE number default 0,
pUNICODE_CAS number default 0,
pUNIX_LINES number default 0)
return varchar2_table;
Функция возвращает коллекцию строк, удовлетворяющих регулярному выражению. Параметры аналогичны предыдущей функции, но добавлен еще необязательный параметр pMaxCount - максимальное кол-во возвращенных подстрок, если он не указан или равен нулю, то возвращаются все найденные.
Пример поиска слов с буквой "е":
select * from table(xt_regexp.get_matches('Пожалуйста, сьешьте еще мягких французских булочек!','\S*е\S*'));

Функция аналогична предыдущей - get_matches, но возвращает строку, состоящую из соединенных найденных подстрок через указанный в параметре разделитель.function join_matches(pStr varchar2,pPattern varchar2,pDelim varchar2 default ';',
pCANON_EQ number default 0,
pCASE_INSENSITIVE number default 0,
pCOMMENTS number default 0,
pDOTALL number default 0,
pMULTILINE number default 0,
pUNICODE_CAS number default 0,
pUNIX_LINES number default 0)
return varchar2;
function replace_all(pStr varchar2,pPattern varchar2,pReplacement varchar2)
return varchar2
Функция заменяет все подстроки удовлетворяющие регулярному выражению на строку указанную в третьем параметре.
Пример - заменяем запятые перед которыми слово "Пожалуйста" на "$1 Scott!!!!":
Результат:select xt_regexp.replace_all('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=Пожалуйста)(,)','$1 Scott!!!!') from dual;
Пожалуйста, Scott!!!! сьешьте еще мягких французских булочек!
Данная функция полностью аналогична предыдущей, кроме того, что она заменяет только первое соответствие.function replace_first(pStr varchar2,pPattern varchar2,pReplacement varchar2)
return varchar2
UPD: Добавил функцию longest_overlap - возвращает максимальное совпадение в двух строках:
Пример: select xt_regexp.longest_overlap('1234567890','abcdef3456fgnk') from dual
Примеры использования:
select 1,t1.* from table(xt_regexp.split('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=,)\s')) t1
union all
select 2,t2.* from table(xt_regexp.split('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=\S)\s',3)) t2
union all
select 3,t3.* from table(xt_regexp.split_simple('Пожалуйста, сьешьте еще мягких французских булочек!',' ')) t3
union all
select 4,xt_regexp.replace_all('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=Пожалуйста),','!!!!') from dual
union all
select 5,xt_regexp.replace_all('Пожалуйста, сьешьте еще мягких французских булочек!','(?<=Пожалуйста)(,)','$1 Scott!!!!') from dual
union all
select 6,xt_regexp.replace_first('Пожалуйста, Пожалуйста, сьешьте еще мягких французских булочек!','(?<=Пожалуйста)(,)','$1 Scott!!!!') from dual
union all
select 7,xt_regexp.replace_char('Пожалуйста, сьешьте еще мягких французских булочек!',',','!') from dual
union all
select 8,t8.* from table(xt_regexp.get_matches('Пожалуйста, сьешьте еще мягких французских булочек!','\s.+\s')) t8
union all
select 9,xt_regexp.join_matches('Пожалуйста, сьешьте еще мягких французских булочек!','[a-zA-Zа-яА-Я]+',';') from dual
--select 10,case when to_char(xt_regexp.matches('Москва, Урюпинск, Баден-Баденasd','.*()(.+)-\1.*')) then 1 else 0 end from dual
Несколько вариантов разбиения строк на подстроки по разделителю:
1. С помощью регулярных выражений c 10g:
with t as (
select 'a,d,f,g,h,j,kassd,asdf,123,455,asd' str,',' delimiter from dual
union all select '6|7|8|9' str,'|' delimiter from dual
)
select *
from t
,table(
cast(
multiset(
select regexp_substr(t.str,'[^'||t.delimiter||',]+',1,level) -- с 10g
from dual
connect by regexp_substr(t.str,'[^'||t.delimiter||',]+',1,level) is not null -- c 10g
-- для 11g лучше будет:
-- level<=regexp_count(t.str,',')
) as sys.odcivarchar2list
)
) substring
2. Для древних версий с помощью с substr+instr:
with t as (
select 'a,d,f,g,h,j,kassd,asdf,123,455,asd' str,',' delimiter from dual
union all select '6|7|8|9' str,'|' delimiter from dual
)
select t.*
,t2.*
,substr(t.str
,lag(t2.column_value,1,0)over(partition by t.str order by t2.column_value)+1
,t2.column_value-lag(t2.column_value,1,0)over(partition by t.str order by t2.column_value)-1
)
from t
,table(
cast(
multiset(
select instr(t.str||t.delimiter,t.delimiter,1,rownum)
from all_objects
where rownum<=1+length(t.str)-length(translate(t.str,'Z'||t.delimiter,'Z'))
) as sys.odcinumberlist
)
) t2
3. С помощью парсера xml(ненадежно):
with t as (
select 'a,d,f,g,h,j,kassd,asdf,123,455,asd' str,',' delimiter from dual
union all select '6|7|8|9' str,'|' delimiter from dual
)
select *
from t
,xmltable(
'/rows/*'
passing xmltype(''||replace(t.str,t.delimiter,' ')||' ')
columns
rn for ordinality
,subst varchar2(4000) path '.'
) t2
4. С помощью функций xml(ненадежно):
with t as (
select 'a,d,f,g,h,j,kassd,asdf,123,455,asd' str,',' delimiter from dual
union all select '6|7|8|9' str,'\|' delimiter from dual
)
select *
from t
,xmltable(
('for $r in ora:tokenize(.,"'||t.delimiter||'")
return {$r} ')
passing t.str
columns subst varchar2(4000) path '.'
)
