【问题标题】:PL/SQL Procedure. avoiding SQL duplicationPL/SQL 过程。避免 SQL 重复
【发布时间】:2018-03-13 06:42:41
【问题描述】:

我正在使用 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;

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

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

【问题讨论】:

  • 把你的那些 SQL 转换成 views 怎么样?
  • 视图是封装查询的最传统方式。这能满足您的需求吗?
  • 为什么不直接将结果插入带有 Id 或日期的表中,然后在函数调用后读取结果
  • 什么代码重复?我不明白你想要避免的。
  • @Gordon Linoff - 例如,如果我有 150 行 SQL 查询并且我需要找到 COUNT 但后来需要在减号语句中使用相同的查询,我不想必须重复完整的 150 行查询。

标签: sql oracle plsql


【解决方案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

【讨论】:

  • 太好了,谢谢。最后,意见跑得有点慢。所以我选择设置一些空表结构。然后,当我运行 mathguy 的 PLSQL 块时,我调用另一个填充表的存储过程,然后我可以运行检查,截断表并继续下一个查询。总而言之,这让我避免了代码重复并保持运行时间相当快。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 2019-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-27
相关资源
最近更新 更多