【问题标题】:PL/SQL query IN comma deliminated stringPL/SQL 查询 IN 逗号分隔字符串
【发布时间】:2011-10-25 19:49:47
【问题描述】:

我正在 Oracle APEX 中开发一个应用程序。我有一个用逗号分隔的用户 ID 字符串,看起来像这样,

45,4932,20,19

这个字符串存储为

:P5_USER_ID_LIST

我想要一个可以找到此列表中所有用户的查询,我的查询如下所示

SELECT * FROM users u WHERE u.user_id IN (:P5_USER_ID_LIST);

我不断收到 Oracle 错误:无效号码。但是,如果我将字符串硬编码到查询中,它就可以工作。像这样:

SELECT * FROM users u WHERE u.user_id IN (45,4932,20,19);

有人知道为什么这可能是个问题吗?

【问题讨论】:

    标签: oracle plsql oracle-apex


    【解决方案1】:

    由于您将用户 ID 存储为字符串,因此您可以使用 Like 轻松匹配字符串,如下所示

    SELECT * FROM users u WHERE u.user_id LIKE '%'||(:P5_USER_ID_LIST)||'%'
    

    例如

    :P5_USER_ID_LIST  = 45,4932,20,19
    

    您的查询肯定会返回与用户表匹配的 1 个用户 ID 中的任何一个

    这一定会解决你的问题,享受

    【讨论】:

      【解决方案2】:

      Tony Andrews 的解决方案适合我。该进程应添加到“页面处理”>>“提交后”>>“进程”中。

      【讨论】:

        【解决方案3】:

        请不要使用:WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%' 因为你会强制使用一个完整的表扫描虽然用户表可能没有那么多,所以影响会很小,但对于企业环境中的其他表,这是一个问题。

        编辑:我编写了一个脚本来演示正则表达式方法和通配符之类的方法之间的区别。正则表达式不仅更快,而且更健壮。

        -- Create table
        create table CSV_TEST
        (
          NUM NUMBER not null,
          STR VARCHAR2(20)
        );
        
        
        create sequence csv_test_seq;
        
        begin
          for j in 1..10 loop
            for i in 1..500000 loop
             insert into csv_test( num, str ) values ( csv_test_seq.nextval, to_char( csv_test_seq.nextval ));
            end loop;
            commit;
          end loop;
        end;
        /
        
        -- Create/Recreate primary, unique and foreign key constraints 
        alter table CSV_TEST
          add constraint CSV_TEST_PK primary key (NUM)
          using index ;
        alter table CSV_TEST
          add constraint CSV_TEST_FK unique (STR)
          using index;
        
        select sysdate from dual;
        select *
        from csv_test t
        where t.num in ( Select Regexp_Substr('100001, 100002,   100003   ,      100004, 100005','[^,]+', 1, Level) From Dual
                         Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
        select sysdate from dual;
        
        select *
        from csv_test t
        where ('%,' || '100001,100002,   100003,  100004  ,100005' || ',%') like '%,' || num || ',%';
        select sysdate from dual;
        select *
        from csv_test t
        where t.num in ( Select Regexp_Substr('100001, 100002,   100003   ,      100004, 100005','[^,]+', 1, Level) From Dual
                         Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
        select sysdate from dual;
        
        select *
        from csv_test t
        where ('%,' || '100001,100002,   100003,  100004  ,100005' || ',%') like '%,' || num || ',%';
        select sysdate from dual;
        
        drop table csv_test;
        drop sequence csv_test_seq;
        

        【讨论】:

          【解决方案4】:

          如果可能,最好的办法是不要将您的用户 ID 存储在 csv 中!将它们放在表中或将其放在数组中等。您不能将 csv 字段绑定为数字。

          【讨论】:

            【解决方案5】:

            我已经多次遇到这种情况,这是我用过的:

            SELECT * 
              FROM users u 
             WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%'
            

            我使用了 like 运算符,但您必须在这里注意一个方面:您的项目 P5_USER_ID_LIST 必须是“,45,4932,20,19”,这样like 将与确切的数字“',45, '"。

            当这样使用它时,选择不会出错让我们说:5 和 15、155、55。

            试试看,告诉我结果如何;)

            干杯, 亚历克斯

            【讨论】:

            • 您不需要或不需要 TO_CHAR :P5_USER_ID_LIST
            【解决方案6】:

            一个更简单的解决方案是使用instr:

            SELECT * FROM users u 
            WHERE instr(',' || :P5_USER_ID_LIST ||',' ,',' || u.user_id|| ',', 1) !=0;
            

            技巧:

            ',' || :P5_USER_ID_LIST ||','
            

            让你的字符串,45,4932,20,19,

            ',' || u.user_id|| ','
            

            拥有即,32,并避免选择32,4932,

            【讨论】:

              【解决方案7】:

              这是一个问题的原因是您不能按照自己想要的方式绑定 in 列表,而且几乎每个人在学习 Oracle(可能还有 SQL!)时都至少犯过一次这个错误。

              当您绑定字符串 '32,64,128' 时,它实际上变成了如下查询:

              select ...
              from t
              where t.c1 in ('32,64,128')
              

              对于 Oracle,这完全不同于:

              select ...
              from t
              where t.c1 in (32,64,128)
              

              第一个示例在 in 列表中有一个字符串值,第二个示例在 in 列表中有 3 个数字。您收到无效数字错误的原因是 Oracle 尝试将字符串 '32,64,128' 转换为数字,但由于字符串中的逗号,它无法执行此操作。

              这个“我如何绑定一个列表”问题的变体最近在这里出现了好几次。

              一般来说,无需求助于任何 PLSQL,担心 SQL 注入或未正确绑定查询,您可以使用此技巧:

              with bound_inlist
                as
                (
                select
                  substr(txt,
                         instr (txt, ',', 1, level  ) + 1,
                         instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 )
                         as token
                  from (select ','||:txt||',' txt from dual)
                connect by level <= length(:txt)-length(replace(:txt,',',''))+1
                )
                select *
              from bound_inlist a, users u
              where a.token = u.id;
              

              【讨论】:

                【解决方案8】:

                绑定变量绑定 a 值,在本例中为字符串 '45,4932,20,19'。您可以按照 Randy 的建议使用动态 SQL 和连接,但您需要非常小心,用户无法修改此值,否则您会遇到 SQL 注入问题。

                更安全的方法是将 ID 放入 PL/SQL 进程中的 Apex 集合中:

                declare
                    array apex_application_global.vc_arr2;
                begin
                    array := apex_util.string_to_table (:P5_USER_ID_LIST, ',');
                    apex_collection.create_or_truncate_collection ('P5_ID_COLL');
                    apex_collection.add_members ('P5_ID_COLL', array);
                end;
                

                然后将您的查询更改为:

                SELECT * FROM users u WHERE u.user_id IN 
                (SELECT c001 FROM apex_collections
                 WHERE collection_name = 'P5_ID_COLL')
                

                【讨论】:

                • apex_collection.create_or_truncate_collection 给我cannot insert NULL into ("APEX_040000"."WWV_FLOW_COLLECTIONS$"."SESSION_ID")
                • @OleksandrPapchenko 您只能在 APEX 会话中调用 apex_collection 包。
                【解决方案9】:

                创建原生查询而不是使用“createQuery/createNamedQuery”

                【讨论】:

                  【解决方案10】:

                  您需要将其作为动态 SQL 运行。

                  创建整个字符串,然后动态运行它。

                  【讨论】:

                  • 不要这样做——首先,如果查询频繁地为许多 user_id 运行,它将用许多不可用的游标淹没共​​享池,并且很快整个数据库就会出现解析问题。如果您不非常小心,也存在很大的 SQL 注入风险。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-09-29
                  • 1970-01-01
                  • 2017-09-09
                  • 2017-12-24
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多