各位 PL/SQL 专家,这也许是一个无法回答的问题,但也许有人有一个出色的解决方案。
今天,我发现自己复制粘贴了一条非常长的 SQL 语句,以便在一种情况下可以有条件地加入另一个表,而在另一种情况下则不能这样做。显然,这让我这个程序员很恼火,他现在有两个 SQL 副本需要维护:
IF <condition>
THEN
FOR rec_data IN (SELECT <complex SQL, pages long>)
LOOP
PIPE ROW(....);
END LOOP;
ELSE
FOR rec_data IN (SELECT *
FROM (SELECT <same complex SQL, pages long> ) x,
<another table> y)
LOOP
PIPE ROW(....);
END LOOP;
END IF;
如何避免拥有相同 SQL 的两个副本?
选项:
使用动态 SQL 并有条件地将主 SQL 与执行附加连接的另一个查询 block 包装在一起。缺点:动态 SQL 更难阅读和使用,因为所有转义单引号、维护绑定(bind)变量列表等。而且,它是字符串操作,感觉编码很差。而且,我必须定义一个记录类型,其中包含要从中获取的所有列。更多的工作,更多的冗余编码(我需要为数十个函数执行此操作,而不仅仅是一个,所以这很重要)。
创建另一个运行核心 SQL 并返回行的管道函数,然后在我的顶级函数中以两种不同的方式进行查询。缺点:我的代码现在被拆分为一个完全不同的对象,现在我不仅必须为行创建类型,而且它们必须是 SQL 类型。很多定义只是为了一个函数的本地使用。
创建一个全局临时表并使用核心 SQL 加载它。直接从中选择或有条件地选择并加入。缺点:现在我有一个在代码对象外部使用硬编码列定义定义的表,仅供该代码对象使用。请记住,我有几十个这样的函数要编写,而且我不想要对象意大利面条。
为核心 SQL 创建 View 。有条件查询。缺点:要维护单独的对象,而且无法将变量深入到 View 中。
无条件地仅使用较长的版本(带有条件连接的版本),但在其中使用奇特的 CASE/DECODE 来有效禁用连接(例如
DECODE(<condition>,x.join_key,NULL) = y.join_key
)。缺点:这相当 hacky,并且如果您有条件加入的附加“表”是昂贵的 PL/SQL 管道函数,则可能不太容易获得性能优势。我试图避免在不需要输出时调用该函数。
迫切需要的是避免复制粘贴,避免字符串操作,并避免仅仅为了获取本地所需的内容而定义列定义。就像我们需要做这样的事情(从我的 PL/SQL 游标变量中读取 SQL...这是伪代码,我知道你不能按照编写的那样执行此操作!)
DECLARE
CURSOR cur_data IS
SELECT <complex SQL>;
BEGIN
IF <condition>
THEN
FOR rec_data IN (SELECT *
FROM cur_data)
LOOP
PIPE ROW(....);
END LOOP;
ELSE
FOR rec_data IN (SELECT *
FROM cur_data,
other_table)
LOOP
PIPE ROW(....);
END LOOP;
END IF;
有什么疯狂的、很棒的想法吗?
最佳答案
动态 SQL 是处理样板代码的一项很好的技术。在大多数编程语言中,动态代码都是有问题的,因为很难推断编程语言和环境,而且字符串操作很丑陋。 Oracle 有一些功能可以缓解这些问题。
Oracle提供了数据字典和PL/Scope等工具来让我们更容易地推理我们的数据库环境和代码。通过使用 ALL_TAB_COLUMNS
等 View 的简单 SQL 语句,可以轻松理解 SQL 对象。 .
Oracle 具有可以显着清理字符串操作代码的功能。我们可以通过组合多行字符串、替代引用机制和简单的模板系统来构建更清晰的代码,而不是无休止的串联和使用大量的引号。
多行字符串意味着简单地使用 native 行尾而不是连接 CHR(10)||CHR(13)
。 (我很困惑为什么 2023 年的某些语言不支持如此简单的概念。)替代引用语法允许我们指定自己的分隔符,例如 q'!...!'
, q'[...]'
,和q'<...>'
- 不再有双引号。模板化不需要花哨的引擎,只需要简单的变量语法和 REPLACE
功能。
declare
-- Create a SQL template with well-formatted code.
-- The variables will be replaced later.
-- In trivial examples, templating may need more lines of code than concatenation,
-- but for REAL code, defining the template in one place up front is a life-saver.
v_sql clob :=
q'[
insert into some_table
select 'a' b, 'c' d, '#VALUE1#'
from #TABLE1#
#WHERE1#
]'
begin
-- Set variables.
-- (In real code, you may need to worry about SQL injection and the performance of
-- using literals instead of bind variables.)
v_value1 := 'A';
v_table1 := 'dual';
v_where := 'where 1=1'
...
-- Replace the variables here.
v_sql := replace(replace(replace(v_sql
, '#VALUE1#', v_value1)
, '#TABLE1#', v_table1)
, '#WHERE1#', v_where);
-- Printing the SQL is useful for debugging.
dbms_output.put_line(v_sql);
-- Run the SQL.
-- (This will get more complicated for bind variables and retrieving results.)
execute immediate v_sql;
end;
/
关于oracle - 如何在 PL/SQL 中重用查询 block 以最大程度地减少代码样板和对象意大利面,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75292627/