繁体   English   中英

Oracle SQL:将WITH…SELECT…重构为函数的最简洁方法是什么

[英]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子句下,因为每个子查询都相当复杂。

到目前为止,我仅提出了以下内容作为简化示例。

  • 它涉及将WITH子句放在游标循环中
  • 但是它是否在每个循环中运行WITH子句,这在性能方面会引起很大的担忧? 当以函数形式编写时,以及当我使用SqlDeveloper的“解释计划”函数运行它时,它根本不会透露很多有用的信息。
  • 有没有更好/更清洁/更高效的方式来做到这一点?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM