【问题标题】:Alternate of using NVL with IN clause in Oracle 11g在 Oracle 11g 中使用 NVL 和 IN 子句的替代方法
【发布时间】:2017-01-17 07:23:26
【问题描述】:

我有一个工作场所问题,我正在寻找一个简单的解决方案。 我正在尝试在较小的场景中复制它。

简单的问题

我想在in 子句中使用nvl。目前我有一个由名称组成的输入字符串。它用于下面的 where 子句中

and column_n = nvl(in_parameter,column_n)

现在我想在同一个输入参数中传递多个逗号分隔的值。因此,如果我将= 替换为in,并将输入的逗号分隔字符串转置为行,我将无法使用nvl 子句。

问题详解

让我们考虑一个 Employee 表 emp1。

Emp1
+-------+-------+
| empno | ename |
+-------+-------+
|  7839 | KING  |
|  7698 | BLAKE |
|  7782 | CLARK |
+-------+-------+

现在这是现有存储过程的简单版本

create or replace procedure emp_poc(in_names IN varchar2)
as
 cnt integer;
begin
 select count(*) 
  into cnt 
 from emp1 
 where 
 ename = nvl(in_names,ename); --This is one of the condition where we will make the change.
dbms_output.put_line(cnt);
end;

所以这将给出作为输入参数传递的员工数量的计数。但是如果我们传递 null,它将返回员工总数,因为 nvl

所以这些过程调用将呈现给定的输出。

Procedure Call          Output
exec emp_poc('KING')    1
exec emp_poc('JOHN')    0
exec emp_poc(null)      3

现在我想要实现的是添加另一个功能。所以exec emp_poc('KING,BLAKE') 应该给我2。所以我想出了一种将逗号分隔的字符串拆分为行并在过程中使用的方法。

如果我将下面的where 子句更改为in

create or replace procedure emp_poc2(in_names IN varchar2)
as
 cnt integer;
begin
 select count(*) 
  into cnt 
 from emp1 
 where 
ename in (select trim(regexp_substr(in_names, '[^,]+', 1, level)) 
        from dual 
            connect by instr(in_names, ',', 1, level - 1) > 0
          );
dbms_output.put_line(cnt);
end;

所以exec emp_poc2('KING','BLAKE') 给了我2。但是通过null 将得到0 的结果。但是我想得到3 就像emp_proc 的情况一样 而且我不能将nvlin 一起使用,因为它希望子查询返回单个值。

我能想到的一种方法是根据输入参数在变量中重建整个查询,然后使用execute immediate。但是我正在使用一些变量来收集值,而使用execute immediate 很难实现它。

我再次强调,这是一个复杂过程的简单版本,我们在其中捕获许多变量,它连接许多表,并且在 where 子句中有多个 AND 条件。

关于如何完成这项工作的任何想法。

【问题讨论】:

  • 输入变量 (in_names) 数据类型是字符串还是必须是集合/容器数据类型?
  • 这根本不是强制性的。无论哪种效果更好,我都可以使用它。
  • 逗号分隔的列表在 SQL 或 PL/SQL 中并不像在其他一些语言中那样 - 它只是一个恰好包含逗号的文本字符串。没有内置方法可以将其视为文字列表或元素数组或其他任何东西。
  • @WilliamRobertson 你能说出一种语言,其中包含逗号的文本字符串不仅仅是一个文本字符串吗?还是我看错了你的评论?
  • 我想到了 JavaScript 和 Bash。当然,它们并不完全将包含逗号的字符串变量视为数组,但它们以 SQL 没有的方式非常透明地转换它们。

标签: sql oracle plsql oracle11g


【解决方案1】:

这可能对你有帮助

CREATE OR REPLACE PROCEDURE emp_poc2(in_names IN varchar2)
AS
 cnt integer;
BEGIN
    SELECT COUNT(*)  INTO cnt 
    FROM emp1 
    WHERE 
        in_names IS NULL
        OR ename IN (
            SELECT TRIM(REGEXP_SUBSTR(in_names, '[^,]+', 1, level)) 
            FROM dual 
            CONNECT BY INSTR(in_names, ',', 1, level - 1) > 0
              );
    dbms_output.put_line(cnt);
END;

其他方式可以使用 IF ELSE 或 UNION ALL

【讨论】:

  • 抱歉回复晚了。我能够成功地对其进行测试。谢谢!
【解决方案2】:

如果您的实际代码要复杂得多,那么改用适当的集合类型可能会大大提高您的代码可读性。

在下面的示例中,我创建了一个用户定义类型str_list_t,它是一个真正的字符串集合。

我还在 sql 查询中使用公用表表达式 (CTE) 来增强可读性。在这个简单的示例中,CTE 对可读性的好处并不明显,但对于所有重要的查询来说,它都是一个有价值的工具。

测试数据

create table emp1(empno number, empname varchar2(10));

insert into emp1 values(5437, 'GATES');
insert into emp1 values(5438, 'JOBS');
insert into emp1 values(5439, 'BEZOS');
insert into emp1 values(5440, 'MUSK');
insert into emp1 values(5441, 'CUBAN');
insert into emp1 values(5442, 'HERJAVEC');
commit;

支持的数据类型

create or replace type str_list_t is table of varchar2(4000 byte);
/

子程序

create or replace function emp_count(p_emps in str_list_t) return number is
  v_count number;
  v_is_null_container constant number :=
    case
      when p_emps is null then 1
      else 0
    end;
begin
  -- you can also test for empty collection (that's different thing than a null collection)
  with
  -- common table expression (CTE) gives you no benefit in this simple example
  emps(empname) as (
    select * from table(p_emps)
  )
  select count(*)
    into v_count
    from emp1
   where v_is_null_container = 1
      or empname in (select empname from emps)
  ;
  return v_count;
end;
/
show errors

示例运行

SQL> select 2 as expected, emp_count(str_list_t('BALLMER', 'CUBAN', 'JOBS')) as emp_count from dual
union all
select 0, emp_count(str_list_t()) from dual
union all
select 6, emp_count(null) from dual
;

  EXPECTED  EMP_COUNT
---------- ----------
         2          2
         0          0
         6          6

【讨论】:

  • 简单地将名称添加到表中并进行连接怎么样?
  • @user272735 :感谢您的回复和建议。我一定会检查一下,然后看看如何将其纳入实际程序中。
  • @BobC 我猜你是在建议一个全局临时表?听起来很聪明——我从来没有想过这个选项!事实上,我不记得我曾经使用过 GTT。我需要对此做一个心理记录。当我遇到这种情况时,过滤列表通常是一个简短的 id 编号列表(最多十个),所以 GTT 听起来有点矫枉过正(进入磁盘而不是留在内存中)。然而,对于更大的数据量来说,这听起来是一个选择。
  • 一个 GTT 甚至一个带有键的常规堆表。它可能无论如何都不会进入磁盘,而是在缓冲区缓存中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
  • 1970-01-01
  • 2018-03-08
相关资源
最近更新 更多