Показаны сообщения с ярлыком pl/sql. Показать все сообщения
Показаны сообщения с ярлыком pl/sql. Показать все сообщения

Анализ PL/SQL кода в Oracle 11g (Перевод "Zoom In on Your Code" By Steven Feuerstein)

0 коммент.

Используйте PL/Scope для анализа вашего PL/SQL кода

С момента первого релиза PL/SQL, Oracle предоставляет набор представлений позволяющих разработчику получать информацию о PL/SQL объектах. Эти представления помогают нам понимать и анализировать наш код. Oracle Database 11g предоставляет еще более мощный аналитический инструмент - PL/Scope, собирающий информацию обо всех идентификаторах в вашем PL/SQL коде, которая затем доступна через представления словаря данных. Эти представления могут помочь отследить использование каждой переменной в определении, ссылках, вызовах, а также местоположение каждого вхождения переменной в исходном коде.

Благодаря PL/Scope разработчики могут значительно лучше и легче выполнять анализ кода. Некоторые полезные приемы его использования и будут рассмотрены в данной статье.

"Лишние" типы

0 коммент.

Как же я не люблю, когда создают по 100-200 одинаковых типов с различным названием, а особенно ненавижу, когда когда они еще и бестолково называются и содержат в себе другие такие же дублирующие типы... Ну к чему создавать по двести различных типов коллекций - можно ведь создать набор наиболее частых и, прежде чем создавать новый, посмотреть какие уже есть. И уж тем более когда уже есть "predefined" типы, как, например, ku$_vcnt, который часто используют в примерах.

Таких типов достаточно много, вот еще ku$_ObjNumPairList:

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 в коллекцию на лету

1 коммент.

Маленький ночной бред на заданный вопрос на sql.ru:
Господа, подскажите, есть ли способ преобразовать реф.курсор в коллекцию 'налету' в селекте?
Т.е есть скажем объектный тип 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 - много, хотелось бы не городить на каждый по однотипной ф-ии,
а преобразовать прямо в селекте, либо обойтись одной универсальной ф-ией для всех типов курсоров.
Есть мысли, как?
В принципе задача идиотская, конечно, но захотелось побаловаться, поэтому и "набредил" извращение с коллекцией sys.anydata. А вообще, если иметь ввиду не только простые селекты объектов, а вообще на любые запросы, то их не обернуть так просто в anydata не заводя своего типа...
В общем, вот:
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;

PL/SQL challenge. Утешительный приз

1 коммент.


Вот что, оказывается, финалисты получают :)
Своего рода утешительный приз за 29-е место из 34 финалистов :(

PS. Все-таки перфекционизм - зло, одни расстройства из-за него...

УРА!

0 коммент.

Я вышел в плейофф PL/SQL Challenge! :) Еще бы войти в десятку :)

Типы-перечисления в PL/SQL

0 коммент.

Хотел сейчас поискать что-нибудь интересное про варианты реализации типов-перечислений для pl/sql и наткнулся на пост Pete Finnigan: удивительно, что такой серьезный специалист на полном серьезе сравнивает пакеты standard'a и обычные пользовательские - что позволено быку...

Вообще грустно, что нет красивого и удобного решения, применимые варианты скудны, если хочется создать нормальный тип-перечисление с возможностью использования вроде:
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'ы...

Объединение диапазонов одним запросом с моделькой

0 коммент.

или Забавы с модельками - 2

На очередное коротенькое баловство вдохновил этот вопрос
Скрипт обновлен, т.к. предыдущий был не оптимальным. Данный вариант лучший среди вариантов с решениями с моделью:
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

2 коммент.

На девелоперской базе очень удобно выгружать весь код и сравнивать его с текущей веткой в системе управления версиями, будь то хоть subversion, git или mercurial. Например, тимлид может каждую пятницу смотреть, что забыли закоммитить и вовремя напомнить программерам описать и закоммитить изменения. К тому же можно там же автоматически сгенерировать документацию по ним для данной ветки с помощью doxygen и положить на девелоперский вебсервер.

Это очень легко реализуется прямо на девелоперской базе выгрузкой кодов в соответствующие директории на сервере и выполнением svn или git status. Для выполнения же команд на сервере есть множество различных функций, но нигде я не нашел функций с возможностью установки таймаута выполнения команды, поэтому написал свою.

Функционала пока маловато, т.к. мало времени и был бы рад, если кто-нибудь присоединился к работе над пакетом, но тем не менее прошу любить и жаловать :)

Пакет 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'))

Установка

  1. Устанавливаем http://github.com/xtender/xt_shell
  2. Выполняем скрипты в следующем порядке:
    • @svn_types.sql
    • @xt_svn.jsp
    • @XT_SVN.pck
  3. Даем гранты на чтение/запись выполнение - примеры в grants.sql
* This source code was highlighted with Source Code Highlighter.

Регулярные выражения Oracle, разбиение строк, поиск подстрок

1 коммент.

Довольно часто встречаются вопросы по поиску, разбиению, замене подстрок. Большинство из них можно решить обычными функциями Oracle, в случае же более сложных сложных правил их перестает хватать. Например, если нужно разбить текст на предложения(используя в качестве определения конца предложения точку), не затрагивая предложений внутри цитат, т.е:
Шел дождь. Иванов оглянулся и произнес: "Я пойду за зонтом. Выходи через полчаса."

В 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')
        );

    В примере разбивается по пробелам, перед которыми есть запятая.

    Еще пример:

    select * from table(
        xt_regexp.split('Пожалуйста, сьешьте еще мягких французских булочек!','\s',3)
        );
    Разбиваем по пробелам с максимумом в 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*'));

  • 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;

    Функция аналогична предыдущей - get_matches, но возвращает строку, состоящую из соединенных найденных подстрок через указанный в параметре разделитель.
  • 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




Примеры использования:

«Click to show Spoiler - click again to hide...»


Несколько вариантов разбиения строк на подстроки по разделителю:
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 '.'
      )