
[英]What is the proper way of formatting date in select query Oracle SQL
[英]Oracle SQL: what is the cleanest way to refactor WITH…SELECT… into a function
因此,我一直在尝试寻找最佳方法来以以下形式在计划脚本中重写大量SQL:
WITH
A AS (...<SUB_QA>...),
B AS (...<SUB_QB>...),
C AS (...<SUB_QC>...),
...
SELECT ... FROM
A
LEFT JOIN B
LEFT JOIN C
LEFT JOIN ...
ON ....
进入功能。 这主要是为了便于在多个位置重用该大块表示的相同逻辑。
约束1:只能使用RECORD而不是创建自定义的TYPE;
约束2:必须将这些子查询(例如,等)的内容保持在WITH子句下,因为每个子查询都相当复杂。
到目前为止,我仅提出了以下内容作为简化示例。
SQL创建数据:
--------PERSON table------------
DROP TABLE Test_Persons;
CREATE TABLE Test_Persons (
PersonID int,
LastName varchar2(255),
FirstName varchar2(255)
);
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(1,'LN_1','FN_1');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(2,'LN_2','FN_2');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(3,'LN_21','FN_2');
--------Salary table------------
DROP TABLE TEST_SALARY_A;
CREATE TABLE TEST_SALARY_A ( -- no 'OR REPLACE' for ORACLE
SalaryID int,
PersonID int,
Amount int,
Tax int,
Bank varchar2(20)
);
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(1, 1, 1000, 300, 'BOA1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(2, 2, 2000, 600, 'JP1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(3, 3, 3000, 900, 'TD1');
--------Address table------------
DROP TABLE TEST_ADDRESS_A;
CREATE TABLE TEST_ADDRESS_A (
AddressID int,
PersonID int,
Address varchar2(255)
);
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(1, 1, 'address1');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(2, 2, 'address2');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(3, 3, 'address3');
commit;
块中的原始SQL:
------------------Original--------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = 'LN_1'
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = 'LN_1'
)
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = 'LN_1';
用FUNCTION重写:
------------------Rewritten in functions with ------------------
------------------Contraint 1: can only use RECORD instead of creating customized TYPE;------------
------------------Contraint 2: have to keep the content of the two subqueries under WITH clause exactly as it is --------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID1 int,
LastName varchar2(255),
Address varchar2(255),
PersonID2 int,
Bank varchar2(20),
Tax int
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
FOR x IN (
-------------------start - WITH clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = last_name
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = last_name
)
-------------------end - WITH clause -------------------
-------------------start - main select-----------------------
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = last_name
-------------------end - main select--------------------------
)
LOOP
SELECT x.tj1_ID, x.LASTNAME, x.ADDRESS, x.tj2_ID, x.BANK, x.TAX
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
编辑:修改示例代码以在WITH子句中具有变量
----------------------Create GLOBAL TEMPORARY TABLE -------------------------
DROP TABLE my_global_temp_table;
CREATE GLOBAL TEMPORARY TABLE my_global_temp_table (
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
)
ON COMMIT DELETE ROWS;
----------------------Create PACKAGE AND FUNCTION -------------------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
--------------------use GLOBAL TEMPORARY TABLE-------------------------
INSERT INTO my_global_temp_table
-------------------start - WITH ... SELECT ... clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid
WHERE tps.LASTNAME = last_name
)
SELECT tj1.PERSONID, tj1.LASTNAME, tj1.ADDRESS, ts.BANK
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_SALARY_A ts ON tj1.PERSONID = ts.PERSONID
WHERE tj1.LASTNAME = last_name;
-------------------end - WITH ... SELECT ... clause --
FOR x IN (
SELECT * FROM my_global_temp_table
)
LOOP
SELECT x.PERSONID, x.LASTNAME, x.ADDRESS, x.BANK
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
--------------------Call the FUNCTION-------------------------
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
编辑:按照@Littefoot建议,尝试使用CREATE GLOBAL TEMP表,但给出“ 17/21 PL / SQL:ORA-00936:缺少表达式”。 我不确定为什么吗?
编辑:纠正了Insert语法,但会收到错误消息“ ORA-14551:无法在查询中执行DML操作”,我相信这是因为我从SELECT调用包含该Insert的函数
如果您不使用变量和其他pl sql构造,则建议您以表或实例化视图的形式破坏子句。 这样,您无需冒着在pl sql块中重写查询逻辑并丢失某些内容的风险。 我建议使用物化视图而不是表来使用物化视图,因为它的优点是您下次加载数据时无需删除表,并且可以对物化视图使用nologging。 这将非常快并且具有最小的风险。
感谢Bhanu Yadav
由于WITH
分解子句中没有动态的内容(即您不使用变量-至少我没有注意到任何变量),因此建议您创建一个视图 (基于WITH
)并在需要时使用它。
如果真正的查询真的很复杂并且需要花费一些时间来执行,则可以创建一个全局临时表( GTT
),最有可能选择在会话期间保留其数据( ON COMMIT PRESERVE ROWS
)对其进行正确索引并存储视图(或WITH
' s)那里的内容。 然后,您将在代码中使用GTT
虽然,Oracle会将查询返回的日期保留在内存中,所以您甚至可能必须真正“执行”一次,但是内存不是无限的,因此...测试它,比较您得到的结果,选择一个似乎可以做最好的。
对我来说, GTT
想法听起来很有希望,但是如果没有实际信息,就很难决定。
[编辑,关于GTT]
从您的观点来看,Oracle的“全局临时表”实际上是“本地”(请注意,如果您使用的是18c(虽然我不认为您可以),则可以创建一个私有临时表)。 使用create global temporary table ...
创建一次。 您插入其中的数据仅对您可见,其他人则看不见。 它仅限于您自己的事务(如果使用ON COMMIT DELETE ROWS
创建)或会话( ON COMMIT PRESERVE ROWS
)。 选择最适合您的那个。
这是什么意思? 这意味着您只需创建一次GTT,即可提供列列表及其数据类型。 每个使用您的过程的用户都将在其中插入自己的数据集(如您所说,您将使用带有LAST NAME参数的查询)并在整个事务(或会话)中使用它。 许多用户可以同时执行此操作,但是-正如我已经说过-每个人只会看到自己的数据。
这是伪代码:
-- create table once. Do NOT create it, drop it, create again tomorrow, drop ...
-- Create it once, use it many times.
create global temporary table gtt_my_data
(id number,
c_name varchar2(20), ...
)
on commit preserve rows;
create index i1_gmd_id on gtt_my_data (id);
-- your procedure
procedure p_myproc (par_last_name in varchar2) is
begin
insert into gtt_my_data (id, c_name, ...)
select id, c_name, ...
from some_table join some_other_table ...
where some_table.last_name = par_last_name;
-- now, do whatever you do. When you need to fetch data from the GTT, do so
select ... into ...
from table_x join gtt_my_data on ...
update ... set some_column = (select another_column
from table_y join gtt_my_data on ...
)
end;
完成后:如果结束会话,数据将从GTT中删除 。 如果需要,可以手动进行操作(删除或截断其内容)。
[编辑#2:插入GTT]
插入错误; 您不插入values
,而是这样的:
INSERT INTO my_global_temp_table
WITH test_joined_1 AS
(SELECT tps.personid,
tps.lastname,
tsd.address
FROM test_persons tps
LEFT JOIN test_address_a tsd ON tps.personid = tsd.personid
WHERE tps.lastname = last_name
)
SELECT tj1.personid,
tj1.lastname,
tj1.address,
ts.bank
FROM test_joined_1 tj1
LEFT JOIN test_salary_a ts ON tj1.personid = ts.personid
WHERE tj1.lastname = last_name;
简化,基于Scott的模式:
SQL> create table test (empno number, deptno number);
Table created.
SQL> insert into test (empno, deptno)
2 with temp as
3 (select empno, deptno from emp)
4 select t.empno, t.deptno
5 from temp t join dept d on d.deptno = t.deptno
6 where d.deptno = 10;
3 rows created.
SQL>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.