【问题标题】:using comma separated values inside IN clause for NUMBER column在 NUMBER 列的 IN 子句中使用逗号分隔值
【发布时间】:2013-08-03 03:11:51
【问题描述】:

我在一个包中有 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 错误。

有人可以帮忙吗。

【问题讨论】:

  • 查询不完整,select本身不是plsql(需要将结果集存入变量)请显示完整查询
  • 试试这个 select * from users_Table where user_id in (select l_userIds);

标签: sql oracle plsql procedure


【解决方案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是什么。

【讨论】:

    【解决方案2】:

    你真的需要返回一个逗号分隔的列表吗?声明一个集合类型通常会好得多

    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 语句,从而产生许多其他性能问题。

    【讨论】:

    • 以这种方式使用集合/表时,IN 子句中 1000 个值的限制是否适用?谢谢。
    • @BobJarvis - 不,它没有。 1000 个值限制仅适用于文字或单个绑定变量,不适用于查询(无论是针对表还是集合)。
    • 嗨 @JustinCave 刚刚尝试过,相信需要在函数内部和在查询中使用集合时进行两次更正 return l_nums; - ...select * from TABLE(get_nums)
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

      请勿使用此解决方案!

      首先,我想删除它,但我认为,看到如此糟糕的解决方案可能会为某人提供信息。像这样使用动态 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;
      /
      

      【讨论】:

      • 像这样执行动态 SQL 是可行的(这就是几年前@Sebas 所建议的)。但是您正在生成不同的、不可共享的 SQL 语句,这会给您的共享池带来很大压力。如果您要频繁运行此代码,则会为您的系统带来性能问题。
      • 非常感谢您的评论。实际上,查找动态 SQL 在性能方面要好得多。
      猜你喜欢
      • 2012-05-15
      • 1970-01-01
      • 1970-01-01
      • 2015-02-20
      • 1970-01-01
      • 1970-01-01
      • 2016-01-04
      • 1970-01-01
      相关资源
      最近更新 更多