PL / SQL过程。避免SQL重复

时间:2018-03-10 11:34:28

标签: sql oracle plsql

我使用的是Oracle数据库,并且有几个大的SQL查询(150行+)。我需要每次运行这些倍数来执行不同的测试。为了最大限度地减少代码重复,我通常会使用with子句。例如,如果我想检查两个查询的结果是否相同并确认计数是非零,我可以使用

WITH STATEMENT_1 AS
(
  SELECT ...
)
, STATEMENT_2 AS
(
  SELECT ...
)
SELECT COUNT(*) FROM STATEMENT_1
UNION ALL
SELECT COUNT(*) FROM STATEMENT_2
UNION ALL
(
  SELECT COUNT(*) FROM
  (
    SELECT * FROM STATEMENT_1
    MINUS
    SELECT * FROM STATEMENT_2
  )
);

这很有效。但是,我现在正在尝试创建一个PL / SQL包来存储和自动化这些查询的破坏。我正在寻找一种避免SQL代码重复的方法。我已经看过这样的答案了

Reusing large SQL queries in stored procedures

CREATE OR REPLACE FUNCTION my_ugly_query() 
                           RETURN SYS_REFCURSOR
AS
  my_cursor_ref SYS_REFCURSOR;
BEGIN
  OPEN my_cursor_ref FOR
       SELECT -- 150+ lines of query;

  RETURN my_cursor_ref;
END;

但是我不想使用游标,因为查询都是基于设置的。

总结:寻找一种在不使用游标的情况下在PL / SQL包中封装大型重复SQL基本查询的方法。

1 个答案:

答案 0 :(得分:1)

观点是答案的一部分。它仍然不允许你写一次,到处使用"。

你想要一个通用的"程序" (查询,函数或过程),您可以向其提供两个查询(存储查询的名称),并将吐出您需要的信息。

我在下面向您展示如何在程序中执行此操作。您使用两个视图名称调用该过程,并打印一条消息,显示计数。在严肃的实施中,你不会使用DBMS_OUTPUT.PUT_LINE来获得你的输出,我并不是在鼓吹;我只是展示如何使用过程实现您的概念,您可以修改其行为以写入文件,或者您可以将其更改为将生成带有计数等的表的函数。

请注意 - 由于该过程使用动态SQL,并且表/视图名称不能作为绑定变量传入,因此必须考虑SQL注入(这是一件坏事)。为了解决这个问题,我将视图名称包含在DBMS_ASSERT.SIMPLE_SQL_NAME中。因此,请确保您的视图具有简单的名称(在技术意义上)。

通用程序

create or replace procedure
        comp_queries ( view_name_1 in varchar2, view_name_2 in varchar2 )
is
  cnt_1    number;
  cnt_2    number;
  cnt_diff number;
begin
  execute immediate 'select count(*) from ' || dbms_assert.simple_sql_name(view_name_1)
    into  cnt_1;
  execute immediate 'select count(*) from ' || dbms_assert.simple_sql_name(view_name_2)
    into cnt_2;
  execute immediate 'select count(*) from (
                        select * from ' || view_name_1 || ' minus 
                        select * from ' || view_name_2 || ')'
    into cnt_diff;
  dbms_output.put_line('Count from ' || upper(view_name_1) || ': ' || cnt_1);
  dbms_output.put_line('Count from ' || upper(view_name_2) || ': ' || cnt_2);
  dbms_output.put_line('Count from ' || upper(view_name_1) || ' MINUS '
                             || upper(view_name_2) || ': ' || cnt_diff);
end;
/

编译并确认它没有问题。

然后通过创建两个视图来测试它。在这里,我写了两个应该提供相同输出的查询:

create view v_deptno_1 as
  select distinct deptno from emp;

create view v_deptno_2 as
  select deptno from emp group by deptno;

所以,让我们检查它们确实产生相同的输出。 (请注意,这比查询等效的完整形式证明更弱;它正是您已经在做的事情,检查它们是否等同于基础表中当前存在的数据。)

首先,由于我们使用DBMS_OUTPUT,我们需要打开服务器输出。然后我调用该过程两次 - 一次使用我们刚刚创建的两个视图名称,一次用模拟SQL注入攻击的方式(也总是测试它!)

SQL> set serveroutput on

SQL> exec comp_queries('v_deptno_1', 'v_deptno_2')

Count from V_DEPTNO_1: 3
Count from V_DEPTNO_2: 3
Count from V_DEPTNO_1 MINUS V_DEPTNO_2: 0

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00

-- SIMULATE SQL INJECTION ATTACK:

SQL> exec comp_queries('v_deptno_1', 'v_deptno_2;delete * from emp')

BEGIN comp_queries('v_deptno_1', 'v_deptno_2;delete * from emp'); END;

*
ERROR at line 1:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 206
ORA-06512: at "INTRO.COMP_QUERIES", line 10
ORA-06512: at line 1

Elapsed: 00:00:00.01