web-dev-qa-db-ja.com

1000を超える値をOracle IN句に入れる方法

静的IN句で1000アイテムというOracle 10gの制限を回避する方法はありますか? IN句で使用するIDの多くのコンマ区切りリストがあります。このリストは1000アイテムを超えることがあり、その時点でOracleはエラーをスローします。クエリはこれに似ています...

select * from table1 where ID in (1,2,3,4,...,1001,1002,...)
94
Aaron Palmer

値を一時テーブルに入れてから、select where id in(temptableからidを選択)を実行します

89
Otávio Décio

ORを使用して複数のINに値を分割できることはほぼ確実です。

select * from table1 where ID in (1,2,3,4,...,1000) or 
ID in (1001,1002,...,2000)
52
Peter Severin

次のフォームを使用してみてください。

select * from table1 where ID in (1,2,3,4,...,1000)
union all
select * from table1 where ID in (1001,1002,...)
46
rics
select column_X, ... from my_table
where ('magic', column_X ) in (
        ('magic', 1),
        ('magic', 2),
        ('magic', 3),
        ('magic', 4),
             ...
        ('magic', 99999)
    ) ...
38
Sergey11g

そもそもどこからidのリストを取得しますか?これらはデータベース内のIDであるため、以前のクエリから取得したものですか

私が過去にこれを見たとき、それはそうであった:-

  1. 参照テーブルがありません。正しい方法は、新しいテーブルを追加し、そのテーブルに属性を設定して結合することです
  2. iDのリストはデータベースから抽出され、その後のSQLステートメント(後で、または別のサーバーなど)で使用されます。この場合、答えはデータベースから抽出しないことです。一時テーブルに保存するか、1つのクエリを記述します。

このSQLステートメントを機能させるだけで、このコードを修正するより良い方法があると思います。詳細を入力すると、いくつかのアイデアが得られます。

8
WW.

... from table(...を使用します。

create or replace type numbertype
as object
(nr number(20,10) )
/ 

create or replace type number_table
as table of numbertype
/ 

create or replace procedure tableselect
( p_numbers in number_table
, p_ref_result out sys_refcursor)
is
begin
  open p_ref_result for
    select *
    from employees , (select /*+ cardinality(tab 10) */ tab.nr from table(p_numbers) tab) tbnrs 
    where id = tbnrs.nr; 
end; 
/ 

これは、ヒントが必要なまれなケースの1つです。それ以外の場合、Oracleは列IDのインデックスを使用しません。このアプローチの利点の1つは、Oracleがクエリを何度もハード解析する必要がないことです。ほとんどの場合、一時テーブルの使用は遅くなります。

編集1手順を簡略化(jimmyorrに感謝)+例

create or replace procedure tableselect
( p_numbers in number_table
, p_ref_result out sys_refcursor)
is
begin
  open p_ref_result for
    select /*+ cardinality(tab 10) */ emp.*
    from  employees emp
    ,     table(p_numbers) tab
    where tab.nr = id;
end;
/

例:

set serveroutput on 

create table employees ( id number(10),name varchar2(100));
insert into employees values (3,'Raymond');
insert into employees values (4,'Hans');
commit;

declare
  l_number number_table := number_table();
  l_sys_refcursor sys_refcursor;
  l_employee employees%rowtype;
begin
  l_number.extend;
  l_number(1) := numbertype(3);
  l_number.extend;
  l_number(2) := numbertype(4);
  tableselect(l_number, l_sys_refcursor);
  loop
    fetch l_sys_refcursor into l_employee;
    exit when l_sys_refcursor%notfound;
    dbms_output.put_line(l_employee.name);
  end loop;
  close l_sys_refcursor;
end;
/

これは出力します:

Raymond
Hans
5
tuinstoel

私もここで解決策を探しました。

クエリする必要があるアイテムの上限数に応じて、アイテムが一意であると仮定すると、クエリを1000アイテムのバッチクエリに分割し、代わりに結果を結合することができます(ここで擬似コード):

//remove dupes
items = items.RemoveDuplicates();

//how to break the items into 1000 item batches        
batches = new batch list;
batch = new batch;
for (int i = 0; i < items.Count; i++)
{
    if (batch.Count == 1000)
    {
        batches.Add(batch);
        batch.Clear()
    }
    batch.Add(items[i]);
    if (i == items.Count - 1)
    {
        //add the final batch (it has < 1000 items).
        batches.Add(batch); 
    }
}

// now go query the db for each batch
results = new results;
foreach(batch in batches)
{
    results.Add(query(batch));
}

これは、通常1000を超えるアイテムを持たないシナリオでは、良いトレードオフになる可能性があります。1000を超えるアイテムを使用すると、「ハイエンド」エッジケースシナリオになるためです。たとえば、1500個のアイテムがある場合、(1000、500)の2つのクエリはそれほど悪くありません。これはまた、各クエリがそれ自体で特に高価ではないことを前提としています。

このwould n'tは、予想されるアイテムの典型的な数がはるかに大きくなった場合、たとえば100000の範囲で-100個のクエリが必要な場合に適切です。もしそうなら、おそらく最も「正しい」ソリューションとして上記で提供されたグローバルテンポラリテーブルソリューションを使用することをより真剣に検討する必要があります。さらに、アイテムが一意でない場合は、バッチ内の重複した結果も解決する必要があります。

4
Mike Atlas

はい、Oracleにとっては非常に奇妙な状況です。

iN句内で2000個のIDを指定すると、失敗します。これは失敗します:

select ... 
where id in (1,2,....2000) 

しかし、単に2000のIDを別のテーブル(たとえば一時テーブル)に入れると、これは機能します:

select ... 
where id in (select userId 
             from temptable_with_2000_ids ) 

あなたができることは、実際にレコードを1000個のレコードに分割し、グループごとに実行することができます。

1
Aaron He

IN句を使用する代わりに、IDをフェッチしている他のテーブルでJOINを使用してみてください。そうすれば、制限について心配する必要はありません。私の側からの単なる考え。

0
Raju

以下は、インラインビューを作成してから選択することにより、制限を回避しようとするPerlコードです。ステートメントテキストは、DUALから各アイテムを個別に選択する代わりに、それぞれ12アイテムの行を使用して圧縮され、すべての列を結合して圧縮解除されます。圧縮解除のUNIONまたはUNION ALLは、いずれにせよ結合する前に一意性を課すIN内にすべて入っているため、ここでは違いはありませんが、圧縮では、UNION ALLを使用して多くの不必要な比較を防ぎます。フィルタリングするデータはすべて整数なので、引用は問題になりません。

#
# generate the innards of an IN expression with more than a thousand items
#
use English '-no_match_vars';
sub big_IN_list{
    @_ < 13 and return join ', ',@_;
    my $padding_required = (12 - (@_ % 12)) % 12;  
    # get first dozen and make length of @_ an even multiple of 12
    my ($a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l) = splice @_,0,12, ( ('NULL') x $padding_required );

    my @dozens; 
    local $LIST_SEPARATOR = ', '; # how to join elements within each dozen
    while(@_){
        Push @dozens, "SELECT @{[ splice @_,0,12 ]} FROM DUAL"
    };  
    $LIST_SEPARATOR = "\n    union all\n    "; # how to join @dozens 
    return <<"EXP";
WITH t AS (
    select $a A, $b B, $c C, $d D, $e E, $f F, $g G, $h H, $i I, $j J, $k K, $l L FROM     DUAL
    union all
    @dozens
 )
select A from t union select B from t union select C from t union
select D from t union select E from t union select F from t union
select G from t union select H from t union select I from t union 
select J from t union select K from t union select L from t
EXP
}

次のように使用します:

my $bases_list_expr = big_IN_list(list_your_bases());
$dbh->do(<<"UPDATE");
    update bases_table set belong_to = 'us'
    where whose_base in ($bases_list_expr)
UPDATE
0