2017-01-17 68 views
1

我有一个工作场所问题,我正在寻找一个简单的解决方案。 我想在较小的场景中复制它。在Oracle 11g中使用NVL和IN子句的替代方案

问题在短期

我想用nvlin子句中。目前我有一个由名字组成的输入字符串。它被用在如下的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。不过,我想要3emp_proc 这样的情况我不能使用nvlin,因为它期望子查询返回单个值。

我能想到的一种方法是在基于输入参数的变量中重建整个查询,然后使用execute immediate。但我正在使用一些变量来收集价值,并且很难通过execute immediate来实现。

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

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

+0

它是强制性的输入变量('in_names')数据类型是一个字符串,或者可以它是一个集合/容器数据类型而不是? – user272735

+0

这不是强制性的。无论哪个更好,我都可以使用它。 – Utsav

+0

逗号分隔的列表在SQL或PL/SQL中并不像其他语言中的那样 - 它只是一个恰好包含逗号的文本字符串。没有内建的方式将其视为文字列表或一系列元素或其他任何东西。 –

回答

4

这可能会帮助你

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

+0

对不起,延迟回复。我能够成功地测试它。谢谢! – Utsav

1

如果你真正的代码要复杂得多,通过使用合适的集合,然后你的代码的可读性会大大增强改为输入。

在下面的例子中,我创建了一个用户定义类型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 
+0

如何简单地将表名添加到表中并进行连接? – BobC

+0

@ user272735:感谢您的回复和建议。我一定会检查出来,然后看看它如何被纳入到实际的程序中。 – Utsav

+0

@BobC我猜你在暗示一个_global临时表_?听起来很聪明 - 这个选择从来没有超过我的想法!事实上,我不记得我曾经使用过GTT。我需要对此做一个精神提示。当我遇到这种情况时,过滤列表通常是一个简短的ID号列表(最大为数十),所以GTT听起来有点过分(去磁盘而不是留在内存中)。不过,这听起来是一个更大数据量的选择。 – user272735