在NUM子列的IN子句中使用逗号分隔值

时间:2013-08-02 00:30:04

标签: sql oracle plsql procedure

我在包中有2个程序。我正在调用一个过程来获取逗号分隔的用户ID列表。

我将结果存储在VARCHAR变量中。现在,当我使用这个逗号分隔列表放入IN子句时,它会抛出“ORA-01722:INVALID NUMBER"异常。

这是我的变量的样子

l_userIds VARCHAR2(4000) := null;

这是我分配值

的地方
l_userIds := getUserIds(deptId);  -- this returns a comma separated list

我的第二个查询就像 -

select * from users_Table where user_id in (l_userIds);

如果我运行此查询,则会出现INVALID NUMBER错误。

有人可以帮忙吗。

4 个答案:

答案 0 :(得分:8)

你真的需要返回以逗号分隔的列表吗?声明集合类型

通常要好得多
CREATE TYPE num_table
    AS TABLE OF NUMBER;

声明一个返回此集合实例的函数

CREATE OR REPLACE FUNCTION get_nums
  RETURN num_table
IS
  l_nums num_table := num_table();
BEGIN
  for i in 1 .. 10
  loop
    l_nums.extend;
    l_nums(i) := i*2;
  end loop;
END;

然后在查询中使用该集合

SELECT *
  FROM users_table
 WHERE user_id IN (SELECT * FROM TABLE( l_nums ));

也可以使用动态SQL(@Sebas演示)。然而,它的缺点是每次调用该过程都会生成一个新的SQL语句,需要在执行之前再次对其进行解析。它还对库缓存施加压力,这可能导致Oracle清除许多其他可重用的SQL语句,这些语句可能会产生许多其他性能问题。

答案 1 :(得分:2)

您可以使用like代替in搜索列表:

select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';

这具有简单的优点(没有附加功能或动态SQL)。但是,它确实排除了user_id上索引的使用。对于一个小桌子,这不应该是一个问题。

答案 2 :(得分:1)

问题是oracle没有解释你作为一个数字序列传递的VARCHAR2字符串,它只是一个字符串。

解决方案是使整个查询成为字符串(VARCHAR2),然后执行它,以便引擎知道他必须翻译内容:

DECLARE
    TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
    aVar T_UT;
BEGIN
    EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...

END;

更复杂但也更优雅的解决方案是将字符串拆分为表TYPE并将其直接用于查询。查看Tom thinks about it

答案 3 :(得分:-1)

请勿使用此解决方案!

首先,我想删除它,但我认为,有人看到这样一个糟糕的解决方案可能会提供信息。像这样使用动态SQL会导致多个执行计划创建 - 在IN子句中每1组数据有1个执行计划,因为没有使用绑定,对于DB,每个查询都是不同的(SGA充满了许多非常类似的执行)计划,每次使用不同的参数运行查询时,在SGA中不必要地使用更多内存。

想要更正确地使用动态SQL编写另一个答案(使用绑定变量),但无论如何 Justin Cave's 答案是最好的。

您可能还想尝试REF CURSOR(我自己没有尝试过那些确切的代码,可能需要一些小调整):

DECLARE
    deptId                  NUMBER := 2;
    l_userIds               VARCHAR2(2000) := getUserIds(deptId);
    TYPE t_my_ref_cursor IS REF CURSOR;
    c_cursor                t_my_ref_cursor;
    l_row                   users_Table%ROWTYPE;
    l_query                 VARCHAR2(5000);
BEGIN
    l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
    OPEN c_cursor FOR l_query;

    FETCH c_cursor INTO l_row;
    WHILE c_cursor%FOUND
    LOOP
        -- do something with your row
        FETCH c_cursor INTO l_row;
    END LOOP;

END;
/

<击>