Oracle PL / SQL Dynamic SQL не очень динамичный? - PullRequest
3 голосов
/ 19 марта 2012

Я пытаюсь написать общую процедуру преобразования данных, которая управляется таблицами, но сталкивается с фундаментальной проблемой связывания данных. Я застрял на том, как ссылаться на данные в столбце, чтобы связать или построить динамический оператор SQL. В частности, я использую rowtype в качестве хранилища данных. Упрощенный пример:

Create or replace Procedure UpdateByColumn(rec tbl%rowtype, colName varchar2) is
  Sqlstmt varchar2(1000);
Begin
  -- this won't work because can't refer to data by column name
  Sqlstmt := 'update tbl set ' || colName || '=' || rec(colName);

  -- this will work but no longer dynamic
  Sqlstmt := 'update tbl set ' || colName || '=' || rec.MSRP;
End;

Реальная проблема, с которой я столкнулся, заключается в том, что даже в пакете DBMS_SQL не существует простого способа извлечения любых данных строки по имени. В то время как Oracle, кажется, пытается добавить множество динамических функций, таких как ANYDATA, ANYTYPE, Piplined, но я не могу найти ни одной, которая позволяла бы выполнять такие простые манипуляции с данными. В частности, «Oracle Dynamic SQL Method 4», по-видимому, нереализуем в PL / SQL, где данные связывания поступают из любых данных столбца в строке таблицы. Конечно, вы можете привязать имя столбца с «жестким кодом», но тогда оно перестает быть динамическим.

Кроме того, тот факт, что мне нужно написать несколько методов UpdateByColumn для каждой таблицы, потому что я не могу передать тип строки ОДНОМУ универсальному методу, который будет принимать любой тип строки, является другим ограничением. Мне нужно использовать тип строки, потому что данные были предварительно выбраны вызывающей стороной и, возможно, изменили некоторые данные.

Или я что-то пропустил?

1 Ответ

3 голосов
/ 19 марта 2012

Я думаю, %ROWTYPE здесь тупик. Насколько я знаю, есть способ извлечь полезные метаданные о переменной PL / SQL.

Но все по-другому, если вы можете использовать абстрактный тип данных (ADT или «объект»). Он более мощный и похож на %ROWTYPE. Но это не так удобно и сделает ваш исходный код немного более сложным. Вам нужно будет предварительно определить объекты и использовать их в вашем SQL.

Например, заменить код следующим образом:

declare
    v_test tbl%rowtype;
begin
    select * into v_test from tbl;
end;
/

с этим:

declare
    v_test2 tbl_type;
begin
    select tbl_type(msrp, some_other_column) into v_test2 from tbl;
end;
/

Если это приемлемо, вы можете использовать динамический PL / SQL для своих обновлений:

--Create table, ADT, and test data
create table tbl(MSRP varchar2(100), some_other_column varchar2(100));

create or replace type tbl_type as object
(
    msrp varchar2(100),
    some_other_column varchar2(100)
);
/
insert into tbl values('1', '1');

--Convert object to ANYDATA, process with dynamic PL/SQL
declare
    my_tbl tbl_type := tbl_type('2', '3');

    procedure UpdateByColumn(p_anydata in anydata, colName in varchar2) is
        v_typename varchar2(30) := p_anydata.getTypeName;
    begin
        execute immediate '
            declare
                v_anydata2 anydata := :anydata;
                v_object '||v_typename||';
                v_dummy pls_integer;
            begin
                v_dummy := v_anydata2.getObject(v_object);
                update tbl set '||colName||' = v_object.'||colName||';
            end;
        ' using p_anydata;
    end;
begin
    updateByColumn(anyData.convertObject(my_tbl), 'MSRP');
end;
/

--Show the new data
select * from tbl;

MSRP    SOME_OTHER_COLUMN
----    -----------------
2       1

UPDATE

%ROWTYPE существует только в PL / SQL, нет способа передать или преобразовать эти значения. Но вы можете сохранить запись как переменную пакета, а затем передать имя и тип этой переменной в вашу функцию. Функция может ссылаться на запись с использованием динамического PL / SQL, а затем может преобразовывать ее в значение, которое будет использоваться SQL.

(Это не решает проблему изменения нескольких столбцов одновременно, это просто демонстрация, показывающая способ динамического использования %ROWTYPE.)

--Create table and test data
create table tbl(MSRP varchar2(100), some_other_column varchar2(100));
insert into tbl values('1', '1');
commit;

--Create another table, tbl2, that will be used to update tbl
--(The tables in this example have similar columns, but that is not 
--actually necessary.)
create table tbl2(MSRP varchar2(100), some_other_column varchar2(100));
insert into tbl2 values('2', '2');
commit;

--New function works by passing in names of global variables and 
--their types, instead of actual values.
create or replace procedure UpdateByColumn(
    p_package_and_variable_name in varchar2,
    p_rowtype in varchar2,
    colName in varchar2) is
begin
    execute immediate '
        declare
            v_rec '||p_rowtype||' := '||p_package_and_variable_name||';
        begin
            update tbl set '||colName||' = v_rec.'||colName||';
        end;
    ';
end;
/

--A test package that calls the function to update tbl.
create or replace package test_package is
    tbl2_rec tbl2%rowtype;
    procedure test_procedure;
end;
/

create or replace package body test_package is
    procedure test_procedure is
    begin
        select * into tbl2_rec from tbl2;
        UpdateByColumn('test_package.tbl2_rec', 'tbl2%rowtype', 'MSRP');
    end;
end;
/

begin
    test_package.test_procedure;
end;
/
...