IN子句Oracle

时间:2016-03-14 13:45:52

标签: sql oracle query-performance

我在一家拥有DW-ETL设置的公司工作。我需要编写一个查询,在WHEN - IN子句中查找超过2500个值,并在WHERE - IN子句中查找超过1000个值。基本上它看起来如下:

SELECT
    ,user_id
    ,CASE WHEN user_id IN ('user_n', +2500 user_[n+1] ) THEN 1
    ELSE 0
    ,item_id
FROM user_table
    WHERE item_id IN ('item_n', +1000 item_[n+1] );

您可能已经知道PL / SQL在IN子句中最多允许1000个值,因此我尝试添加OR - IN子句(如其他stackoverflow线程中所建议的那样):

SELECT
    ,user_id
    ,CASE WHEN user_id IN ('user_n', +999 user_[n+1] )
     OR user_id IN ('user_n', +999 user_[n+1] )
     OR user_id IN ('user_n', +999 user_[n+1] ) THEN 1
     ELSE 0 END AS user_group
    ,item_id
FROM user_table
    WHERE item_id IN ('item_n', +999 item_[n+1] )
    OR item_id IN ('item_n', +999 item_[n+1] );

注意:我知道上面的例子中的数学是错误的,但你明白了

问题是查询的最长执行时间为120分钟,并且作业正在自动终止。所以我搜索了我能找到的解决方案,看起来临时表可能是我正在寻找的解决方案,但是说实话,我发现没有一个例子可以清楚地说明如何在表中包含我想要的值和还有如何在我的原始查询中使用此表。甚至ORACLE文档都没有多大帮助。

另一个潜在的问题是我的权利有限,我看到其他人提到在他们的公司中他们没有权利创建临时表。

我在研究中发现的一些信息:

ORACLE documentation

StackOverflow thread

[StackOverflow thread 2]

我发现的另一个解决方案是使用元组,如THIS thread中提到的(我还没有尝试过),因为另一个用户提到性能似乎受到很大影响。

如何使用临时表或任何人有其他方式处理此限制的任何指导将不胜感激。

5 个答案:

答案 0 :(得分:4)

创建全局临时表,以便不创建撤消日志

CREATE GLOBAL TEMPORARY TABLE <table_name> (
<column_name>  <column_data_type>,
<column_name>  <column_data_type>,
<column_name>  <column_data_type>)
ON COMMIT DELETE ROWS;

然后根据用户列表的到达方式将数据导入保留表,然后运行

select 'INSERT INTO global_temporary_table <column> values '
|| holding_table.column
||';' 
FROM holding_table.column; 

This gives you insert statements as output which you run to insert the data.

然后

SELECT  <some_column>
FROM <some_table>
WHERE <some_value> IN
(SELECT <some_column> from <global_temporary_table>

答案 1 :(得分:2)

使用集合:

CREATE TYPE Ints_Table AS TABLE OF INT;
CREATE TYPE IDs_Table AS TABLE OF CHAR(5);

这样的事情:

SELECT user_id,
       CASE WHEN user_id MEMBER OF Ints_Table( 1, 2, 3, /* ... */ 2500 )
            THEN 1
            ELSE 0
            END
      ,item_id
FROM  user_table
WHERE item_id MEMBER OF IDs_table( 'ABSC2', 'DITO9', 'KMKM9', /* ... */  'QD3R5' );

或者您可以使用PL / SQL填充集合:

VARIABLE cur REFCURSOR;

DECLARE
  t_users Ints_Table;
  t_items IDs_Table;

  f       UTL_FILE.FILE_TYPE;
  line    VARCHAR2(4000);
BEGIN
  t_users.EXTEND( 2500 );
  FOR i = 1 .. 2500 LOOP
    t_users( t_users.COUNT ) := i;
  END LOOP;

  // load data from a file
  f := UTL_FILE.FOPEN('DIRECTORY_HANDLE','datafile.txt','R');
  IF UTL_FILE.IS_OPEN(f) THEN
  LOOP
    UTL_FILE.GET_LINE(f,line);
    IF line IS NULL THEN EXIT; END IF;
    t_items.EXTEND;
    t_items( t_items.COUNT ) := line;
  END LOOP;

  OPEN :cur FOR
    SELECT user_id,
           CASE WHEN user_id MEMBER OF t_users
                THEN 1
                ELSE 0
                END
          ,item_id
    FROM  user_table
    WHERE item_id MEMBER OF t_items;
END;
/

PRINT cur;

或者,如果您使用其他语言来调用查询,则可以将集合作为绑定值(as shown here)传递。

答案 2 :(得分:1)

在PL / SQL中,您可以使用集合类型。你可以这样创建自己的:

create type string_table is table of varchar2(100);

或者使用现有类型,例如SYS.DBMS_DEBUG_VC2COLL,它是VARCHAR2(1000)的表。

现在,您可以为每个列表声明此类型的集合,填充它并在查询中使用它 - 如下所示:

declare
   strings1 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();
   strings2 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();

   procedure add_string1 (p_string varchar2) is
   begin
      strings1.extend();
      strings1(strings.count) := p_string;
   end;

   procedure add_string2 (p_string varchar2) is
   begin
      strings2.extend();
      strings2(strings2.count) := p_string;
   end;
begin
   add_string1('1');
   add_string1('2');
   add_string1('3');
   -- and so on...
   add_string1('2500');

   add_string2('1');
   add_string2('2');
   add_string2('3');
   -- and so on...
   add_string2('1400');

   for r in (
    select user_id
         , case when user_id in table(strings2) then 1 else 0 end as indicator
         , item_id
      from user_table
     where item_id in table(strings1)
    )
   loop
      dbms_output.put_Line(r.user_id||' '||r.indicator);
   end loop;
end;
/

答案 3 :(得分:1)

您可以使用以下示例来了解全局临时表和GTT的类型。

CREATE GLOBAL TEMPORARY TABLE GTT_PRESERVE_ROWS (ID NUMBER) ON COMMIT PRESERVE ROWS;
INSERT INTO GTT_PRESERVE_ROWS VALUES (1);
COMMIT;
SELECT * FROM GTT_PRESERVE_ROWS;
DELETE FROM GTT_PRESERVE_ROWS;
COMMIT;
TRUNCATE TABLE GTT_PRESERVE_ROWS;
DROP TABLE GTT_PRESERVE_ROWS;--WONT WORK IF YOU DIDNOT TRUNCATE THE TABLE OR THE TABLE IS BEING USED IN SOME OTHER SESSION

CREATE GLOBAL TEMPORARY TABLE GTT_DELETE_ROWS (ID NUMBER) ON COMMIT DELETE ROWS;
INSERT INTO GTT_DELETE_ROWS VALUES (1);
SELECT * FROM GTT_DELETE_ROWS;
COMMIT;
SELECT * FROM GTT_DELETE_ROWS;
DROP TABLE GTT_DELETE_ROWS;

但是正如您所提到的,您在excel文件中收到输入,因此您只需创建一个表并在该表中加载数据即可。加载数据后,您可以使用查询的IN子句中的数据。

select * from employee where empid in (select empid from temptable);

答案 4 :(得分:0)

 create temporary table userids (userid int);
 insert into userids(...)

然后是连接或子查询

 select ...
 where user_id in (select userid from userids);
 drop temporary table userids;