Записки жертвы высоких технологий

    Здравствуйте! Мой блог посвящен в большей своей части моей профессиональной деятельности на поприще использования технологий ORACLE для разработки баз данных OLTP и OLAP хранилищ данных. В заметках я периодически размещаю разнообразные SQL, PL/SQL и Java скрипты написанные мной и не только мной, ссылки на интересные источники в сети либо другую полезную информацию, которая каким либо образом касается моей работы с замечательными продуктами компании ORACLE.
    Вы можете связаться со мной по контактному емейлу, если у вас есть какие-либо вопросы, связанные с разработкой баз данных на основе продуктов ORACLE, буду рад помочь вам, если это будет в моих силах.

7 окт. 2015 г.

Получение названия таблиц из произвольного SQL запроса

Проблема: Понадобилось сделать генератор SQL скриптов для переноса данных по таблицам. Простой путь: Нашел в сети топорный шаблон под названием fn_gen_inserts(updates) и вознамерился было попользоваться им, но понял, что код не удовлетворяет моим и без того скромным требованиям к генерации скриптов. У меня SQL скриптами осуществляется перенос данных с сервера разработки на сервер тестирования и промышленный сервер. Поэтому задавать имена целевых таблиц и их владельцев, как того требуют авторы исходных кодов вышеназванных функций, как-то несуразно. Как известно, пакет DBMS_SQL имеет несколько простых и удобных методов описания столбцов заданного динамически SQL запроса, но описания таблиц - нет. Как мне кажется, в случае анализа простых SQL запросов, вполне можно попытаться определить название таблицы и ее владельца в автоматическом режиме.
Задача: По заданному строкой SQL запросу определять название опорной таблицы и ее владельца. Итак, кусочек PL/SQL кода, где переменная p_sql - передаваемый SQL запрос

     ...

    -- 1) Evaluate execution plan for SQL query
    begin
        execute immediate 'explain plan for '||p_sql;
    exception when others then
        raise_application_error(-20001, 'ERROR: SQL query cannot be parsed ('||SQLERRM||')');
    end;
    -- 2) Getting the name of the first table in access order list (99,99%)
    begin
        select table_name into l_base_table_name from (
            select regexp_substr(acc, '^[|]\s*(\w+)\s*[|]',1,1,'i',1) idx, regexp_substr(acc, '[|]\s*((\w|[$#_])+)\s*[|]$',1,1,'i',1) table_name 
            from (
                        select plan_table_output acc from table(dbms_xplan.display(nullnull'BASIC -ROWS -COST -PREDICATE -PARALLEL -PARTITION -PROJECTION -ALIAS -NOTE -REMOTE'))
            ) tabaccinfo
            where tabaccinfo.acc like '%TABLE ACCESS%' order by idx
        ) maintabacc where rownum = 1;
    exception when others then
        raise_application_error(-20002, 'ERROR: Cannot get the name of the source table ('||SQLERRM||')');
    end;
    -- 3) Get primary table owner from SQL query as a first name from the list of possible owners
    begin
        select owner into l_base_table_owner from (
            select owner from all_tables t where t.table_name = l_base_table_name order by decode(t.owner, user12)
        ) t where rownum = 1;
    exception when others then
        raise_application_error(-20003, 'ERROR: Cannot get the owner of the source table ('||SQLERRM||')');
    end;

     ...

    после этого можно обработать переданный запрос в DBMS_SQL, чтобы обработать описания столбцов запроса. 

Разумеется, есть несколько допущений, которые значительно ограничивают применимость данного подхода. Вот они:
1) Совершенно необязательно, что основная таблица запроса будет идти первой в порядке доступа в плане выполнения SQL запроса или в случае выгрузки из пайплайновой функции такой таблицы вообще может не быть. В таких случаях я явно задаю название целевой таблицы
2) Совершенно не факт, что владельца таблицы можно получить просто получив отсортированный список владельцев одноименного объекта. В случае, если владелец объекта не может быть определен таким образом, я задаю его вручную, явно.

В моем случае, для выгрузки данных из таблиц в скрипт, этими допущениями вполне можно пренебречь.