Oracle SQL-模型子句中的可变列数
我正在研究oraclesqlmodel子句。我正在尝试使用这个model子句编写动态oraclesql,它可以适应每次运行不同数量的列。然而,我很难理解,即使使用PL/SQL,如何将其应用于动态/通用查询或过程 这是我正在工作的桌子的大致视图Oracle SQL-模型子句中的可变列数,sql,oracle,Sql,Oracle,我正在研究oraclesqlmodel子句。我正在尝试使用这个model子句编写动态oraclesql,它可以适应每次运行不同数量的列。然而,我很难理解,即使使用PL/SQL,如何将其应用于动态/通用查询或过程 这是我正在工作的桌子的大致视图 OWNER||ACCOUNT_YEAR||ACCOUNT_NAME||PERIOD_1||PERIOD_2||PERIOD_3||PERIOD_4||PERIOD_5||PERIOD_6||.... ----------------------------
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]=。。。要显式引用表中的列,而不是参数或变量,我可以简单地替换为其他内容
如果您能帮助我通过SQL或PLSQL实现这一点,我将不胜感激
如果您理解DBMS_SQL包所演示的SQL的解析、绑定和执行流,就可以了解潜在的障碍
一个游标被打开,一个SQL语句被解析一次。解析后,可以调用descripe\u COLUMNS,它明确地告诉您执行该SQL语句将返回哪些列。从那时起,您可以执行多个绑定和执行,将变量的不同值放入同一语句并重新运行。每次执行之后可能会有一次或多次回迁。bind、execute或fetch都不会影响返回的列数、名称、顺序或数据类型
更改返回列的唯一方法是解析不同的SQL语句
根据最后需要的内容,您可能能够使用复杂的数据类型(如XML或JSON)从同一语句返回具有不同内部结构的数据,甚至在同一语句返回的不同行中返回数据
如果您理解DBMS_SQL包所演示的SQL的解析、绑定和执行流,就可以了解潜在的障碍
一个游标被打开,一个SQL语句被解析一次。解析后,可以调用descripe\u COLUMNS,它明确地告诉您执行该SQL语句将返回哪些列。从那时起,您可以执行多个绑定和执行,将变量的不同值放入同一语句并重新运行。每次执行之后可能会有一次或多次回迁。bind、execute或fetch都不会影响返回的列数、名称、顺序或数据类型
更改返回列的唯一方法是解析不同的SQL语句
根据最终需要,您可能可以使用复杂的数据类型(如XML或JSON)从同一语句返回具有不同内部结构的数据,甚至在同一语句返回的不同行中返回数据。首先,您应该尝试通过将表结构更改为更简单的格式来避免动态列。如果垂直存储数据而不是水平存储数据,SQL会简单得多——使用多行而不是多列 如果您不能更改数据结构,您仍然希望使模型查询尽可能简单,因为使用MODEL子句是一件非常痛苦的事情。使用UNPIVOT将表从列转换为行,运行简化的模型查询,然后根据需要将结果转换回 如果您真的需要纯SQL语句中的动态列,那么您需要使用Gary Myers建议的高级数据类型,或者使用下面的Method4解决方案 示例模式 为了使示例完全可复制,这里是我使用的示例数据,以及我不得不稍微修改的模型查询,以仅引用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;
模型查询:
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
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
...
UNPIVOT方法
本例使用静态代码来演示语法,但如果必要,也可以通过创建临时表的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子句更简单。不列出每个时段的规则,只需按时段代码进行分区:
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 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
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
...
SQL中的动态SQL
如果您真的需要在一个查询中完成这一切,我的开源软件包可以提供帮助。一旦包装完成
如果已安装,则通过传入将生成要运行的查询的查询来调用它
此查询返回与上一个模型查询相同的结果,但将根据表中的列自动进行调整
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%'
)
]'
));
首先,您应该尝试通过将表结构更改为更简单的格式来避免动态列。如果垂直存储数据,SQL会简单得多 d水平方向-使用多行而不是多列 如果您不能更改数据结构,您仍然希望使模型查询尽可能简单,因为使用MODEL子句是一件非常痛苦的事情。使用UNPIVOT将表从列转换为行,运行简化的模型查询,然后根据需要将结果转换回 如果您真的需要纯SQL语句中的动态列,那么您需要使用Gary Myers建议的高级数据类型,或者使用下面的Method4解决方案 示例模式 为了使示例完全可复制,这里是我使用的示例数据,以及我不得不稍微修改的模型查询,以仅引用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;
模型查询:
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
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
...
UNPIVOT方法
本例使用静态代码来演示语法,但如果必要,也可以通过创建临时表的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子句更简单。不列出每个时段的规则,只需按时段代码进行分区:
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 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
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
...
SQL中的动态SQL
如果您真的需要在一个查询中完成这一切,我的开源软件包可以提供帮助。一旦包装完成
如果已安装,则通过传入将生成要运行的查询的查询来调用它
此查询返回与上一个模型查询相同的结果,但将根据表中的列自动进行调整
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%'
)
]'
));