Oracle SQL - переменное количество столбцов в предложении модели - PullRequest
1 голос
/ 29 апреля 2020

Я изучаю предложение Oracle SQL Model. Я пытаюсь написать Dynami c Oracle SQL, который может быть адаптирован для работы с разным количеством столбцов каждый раз, используя это предложение модели. Однако я изо всех сил пытаюсь понять, как я мог бы адаптировать это (даже используя PL / SQL) к динамическому / универсальному c запросу или процедуре

. Вот примерное представление таблицы, над которой я работаю

OWNER||ACCOUNT_YEAR||ACCOUNT_NAME||PERIOD_1||PERIOD_2||PERIOD_3||PERIOD_4||PERIOD_5||PERIOD_6||....
---------------------------------------------------------------------------------------------------
 9640||     2018   ||something  1||   34   ||  444   ||   982  ||  55    ||   42   ||  65    ||        
 9640||     2018   ||something  2||   333  ||  65    ||   666  ||  78    ||   44   ||  55    ||
 9640||     2018   ||something  3||   6565 ||  783   ||   32   ||  12    ||   46   ||  667   ||

Вот что у меня есть:

select OWNER, PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4, PERIOD_5, PERIOD_6, PERIOD_7, PERIOD_8, PERIOD_9, PERIOD_10, PERIOD_11, PERIOD_12, ACCOUNT_YEAR, ACCOUNT_NAME
from DATA-TABLE
where OWNER IN ('9640') and PERIOD_1 is not null 
MODEL  ignore nav 
Return UPDATED ROWS
PARTITION BY (OWNER, ACCOUNT_NAME)
DIMENSION BY (ACCOUNT_YEAR)
MEASURES (PERIOD_1,PERIOD_2, PERIOD_3, PERIOD_4, PERIOD_5, PERIOD_6, PERIOD_7, PERIOD_8, PERIOD_9, PERIOD_10, PERIOD_11, PERIOD_12)
RULES
(
          PERIOD_1[2021] = PERIOD_1[2018] * 1.05,
          PERIOD_2[2021] = PERIOD_2[2018] * 1.05,
          PERIOD_3[2021] = PERIOD_3[2018] * 1.05,
          PERIOD_4[2021] = PERIOD_4[2018] * 1.05,
          PERIOD_5[2021] = PERIOD_6[2018] * 1.05,
          PERIOD_7[2021] = PERIOD_7[2018] * 1.05,
          PERIOD_8[2021] = PERIOD_8[2018] * 1.05,
          PERIOD_9[2021] = PERIOD_9[2018] * 1.05,
          PERIOD_10[2021] = PERIOD_10[2018] * 1.05,
          PERIOD_11[2021] = PERIOD_11[2018] * 1.05,
          PERIOD_12[2021] = PERIOD_12[2018] * 1.05

)
ORDER BY ACCOUNT_YEAR asc;

Как вы можете видеть в разделе мер и правил, в настоящее время я жестко кодирую каждый столбец периода в этом запросе

Я хочу иметь возможность использовать это предложение модели (в частности, гибкую часть части правила, чтобы у меня мог быть запрос, который мог бы быть выполнен, скажем, просто для периода 1 -3 или периода 5-12 ...

Я пытался разобраться в этом, но все примеры показывают левую часть правила (например, PERIOD_12 [2021] = ...), чтобы явно ссылаться на столбец в таблице, а не на параметр или переменную Я могу поменяться на что-то еще просто

Любая помощь о том, как я могу выполнить sh через SQL или PL SQL, будет принята с благодарностью

Ответы [ 2 ]

2 голосов
/ 29 апреля 2020

Во-первых, вам следует избегать динамических столбцов c, изменив структуру таблицы на более простой формат. SQL на намного проще, если вы храните данные по вертикали, а не по горизонтали - используйте несколько строк вместо нескольких столбцов.

Если вы не можете изменить структуру данных, вы все равно хотите Сделайте запрос MODEL как можно более простым, потому что с предложением MODEL очень трудно работать. Преобразуйте таблицу из столбцов в строки, используя UNPIVOT, выполните упрощенный запрос MODEL, а затем преобразовайте результаты обратно, если необходимо.

Если вам действительно нужны динамические c столбцы в чистом виде SQL, вам нужно либо использовать расширенный тип данных, как предложил Гэри Майерс, либо использовать решение Method4 ниже.

Пример схемы

Чтобы сделать примеры полностью воспроизводимыми, вот примеры данных, которые я использовал, вместе с запросом MODEL (который мне пришлось немного изменить, чтобы он ссылался только на 6 переменных и новое имя таблицы).

create table data_table
(
    owner number,
    account_year number,
    account_name varchar2(100),
    period_1 number,
    period_2 number,
    period_3 number,
    period_4 number,
    period_5 number,
    period_6 number
);

insert into data_table
select 9640,     2018   ,'something  1',   34   ,  444   ,   982  ,  55    ,   42   ,  65   from dual union all
select 9640,     2018   ,'something  2',   333  ,  65    ,   666  ,  78    ,   44   ,  55   from dual union all
select 9640,     2018   ,'something  3',   6565 ,  783   ,   32   ,  12    ,   46   ,  667  from dual;

commit;

Запрос MODEL:

select OWNER, PERIOD_1, PERIOD_2, PERIOD_3, PERIOD_4, PERIOD_5, PERIOD_6, ACCOUNT_YEAR, ACCOUNT_NAME
from DATA_TABLE
where OWNER IN ('9640') and PERIOD_1 is not null 
MODEL  ignore nav 
Return UPDATED ROWS
PARTITION BY (OWNER, ACCOUNT_NAME)
DIMENSION BY (ACCOUNT_YEAR)
MEASURES (PERIOD_1,PERIOD_2, PERIOD_3, PERIOD_4, PERIOD_5, PERIOD_6)
RULES
(
          PERIOD_1[2021] = PERIOD_1[2018] * 1.05,
          PERIOD_2[2021] = PERIOD_2[2018] * 1.05,
          PERIOD_3[2021] = PERIOD_3[2018] * 1.05,
          PERIOD_4[2021] = PERIOD_4[2018] * 1.05,
          PERIOD_5[2021] = PERIOD_5[2018] * 1.05,
          PERIOD_6[2021] = PERIOD_6[2018] * 1.05

)
ORDER BY ACCOUNT_YEAR, ACCOUNT_NAME asc;

Результаты:

OWNER  PERIOD_1  PERIOD_2  PERIOD_3  PERIOD_4  PERIOD_5  PERIOD_6  ACCOUNT_YEAR  ACCOUNT_NAME
-----  --------  --------  --------  --------  --------  --------  ------------  ------------
9640       35.7     466.2    1031.1     57.75      44.1     68.25          2021  something  1
9640     349.65     68.25     699.3      81.9      46.2     57.75          2021  something  2
9640    6893.25    822.15      33.6      12.6      48.3    700.35          2021  something  3

Подход UNPIVOT

В этом примере для демонстрации синтаксиса используется код stati c, но при необходимости это также можно сделать более динамичным c, возможно, через PL / SQL, который создает временные таблицы.

create table unpivoted_data as
select *
from data_table
unpivot (quantity for period_code in (period_1 as 'P1', period_2 as 'P2', period_3 as 'P3', period_4 as 'P4', period_5 as 'P5', period_6 as 'P6'));

С неотвеченными данными, предложение MODEL проще. Вместо перечисления правила для каждого периода просто разбейте на PERIOD_CODE:

select *
from unpivoted_data
where OWNER IN ('9640')
    and (OWNER, ACCOUNT_YEAR, ACCOUNT_NAME) in
    (
        select owner, account_year, account_name
        from unpivoted_data
        where period_code = 'P1'
            and quantity is not null
    )
MODEL  ignore nav 
Return UPDATED ROWS
PARTITION BY (OWNER, ACCOUNT_NAME, PERIOD_CODE)
DIMENSION BY (ACCOUNT_YEAR)
MEASURES (QUANTITY)
RULES
(
    QUANTITY[2021] = QUANTITY[2018] * 1.05
)
ORDER BY ACCOUNT_YEAR, ACCOUNT_NAME, PERIOD_CODE;

Результаты:

OWNER  ACCOUNT_YEAR  ACCOUNT_NAME  PERIOD_CODE  QUANTITY
-----  ------------  ------------  -----------  --------
 9640          2018  something  1  P1                 34
 9640          2018  something  1  P2                444
 9640          2018  something  1  P3                982
...

Dynami c SQL в SQL

Если вам действительно нужно сделать все это в одном запросе, мой пакет с открытым исходным кодом Method4 может помочь. Как только пакет установлен, вы вызываете его, передавая запрос, который сгенерирует запрос, который вы хотите запустить.

Этот запрос возвращает те же результаты, что и предыдущий запрос MODEL, но он будет автоматически настроен на основе столбцы в таблице.

select * from table(method4.dynamic_query(
    q'[
        --Generate the MODEL query.
        select
            replace(replace(q'<
                select OWNER, #PERIOD_COLUMN_LIST#, ACCOUNT_YEAR, ACCOUNT_NAME
                from DATA_TABLE
                where OWNER IN ('9640') and PERIOD_1 is not null 
                MODEL  ignore nav 
                Return UPDATED ROWS
                PARTITION BY (OWNER, ACCOUNT_NAME)
                DIMENSION BY (ACCOUNT_YEAR)
                MEASURES (#PERIOD_COLUMN_LIST#)
                RULES
                (
                #RULES#
                )
                ORDER BY ACCOUNT_YEAR, ACCOUNT_NAME asc
            >', '#PERIOD_COLUMN_LIST#', period_column_list)
            , '#RULES#', rules) sql_statement
        from
        (
            --List of columns.
            select
                listagg(column_name, ', ') within group (order by column_id) period_column_list,
                listagg(column_name||'[2021] = '||column_name||'[2018] * 1.05', ','||chr(10)) within group (order by column_id) rules
            from user_tab_columns
            where table_name = 'DATA_TABLE'
                and column_name like 'PERIOD%'
        )
    ]'
));
1 голос
/ 29 апреля 2020

Не.

Вы можете получить представление о лежащем в основе препятствии, если понимаете поток PARSE, BIND, EXECUTE SQL, как продемонстрировано пакетом DBMS_ SQL

https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_SQL.html#GUID -BF7B8D70-6A09-4E04-A216-F8952C347BAF

Открывается курсор и оператор SQL анализируется один раз. После синтаксического анализа может быть вызван DESCRIBE_COLUMNS, который сообщает вам, какие столбцы будут возвращены при выполнении этого оператора SQL. С этого момента вы можете выполнять несколько операций BIND и EXECUTE, помещая разные значения для переменных в одну и ту же инструкцию и перезапускаясь. За каждым EXECUTE может следовать один или несколько FETCH. Ни одно из связывания, выполнения или выборки не может повлиять на то, какие столбцы возвращаются (ни по количеству столбцов, ни по имени, ни по порядку, ни по типу данных).

Единственный способ изменить возвращаемые столбцы - это проанализировать другой SQL оператор.

В зависимости от того, что вы хотите в конце, вы можете использовать сложный тип данных (например, XML или JSON) для возврата данных с разными внутренними структурами из одного и того же оператора (или даже в разных строках, возвращаемых одним и тем же оператором),

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...