静的な IN 句で 1000 項目という Oracle 10g の制限を回避する方法はありますか? IN 句で使用したい多くの ID のカンマ区切りのリストがあります。このリストが 1000 項目を超える場合があり、その時点で Oracle がエラーをスローします。クエリはこれに似ています...
select * from table1 where ID in (1,2,3,4,...,1001,1002,...)
一時テーブルに値を入れてから、select where id in (temptable から id を選択) を実行します。
OR を使用して複数の IN に値を分割できることはほぼ確実です。
select * from table1 where ID in (1,2,3,4,...,1000) or
ID in (1001,1002,...,2000)
次のフォームを使用してみてください。
select * from table1 where ID in (1,2,3,4,...,1000)
union all
select * from table1 where ID in (1001,1002,...)
そもそもIDのリストはどこから取得しますか? これらはデータベース内の ID であるため、以前のクエリから取得したものですか?
私が過去にこれを見たとき、それは次の理由でした:-
このSQLステートメントを機能させるだけで、このコードを作り直すより良い方法があると思います。詳細を提供すると、いくつかのアイデアが得られるかもしれません。
... 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 がクエリを何度もハード解析する必要がないことです。一時テーブルを使用すると、ほとんどの場合遅くなります。
edit 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
私も解決策を探してここにたどり着きました。
クエリを実行する必要があるアイテムの上限数に応じて、アイテムが一意であると仮定すると、クエリを 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 つのクエリはそれほど悪くはありません。これは、各クエリ自体が特に高価ではないことも前提としています。
これは、予想されるアイテムの通常の数がはるかに大きくなった場合 (たとえば、100000 の範囲)、100 のクエリが必要な場合には適切ではありません。もしそうなら、おそらく最も「正しい」解決策として、上記で提供されたグローバル一時テーブルの解決策を使用することをもっと真剣に検討する必要があります。さらに、アイテムが一意でない場合は、バッチ内の重複した結果も解決する必要があります。
はい、オラクルにとって非常に奇妙な状況です。
IN 句内に 2000 個の ID を指定すると、失敗します。これは失敗します:
select ...
where id in (1,2,....2000)
ただし、単に 2000 の ID を別のテーブル (一時テーブルなど) に配置すると、クエリの下で機能します。
select ...
where id in (select userId
from temptable_with_2000_ids )
あなたができることは、実際にレコードを多くの 1000 レコードに分割し、グループごとに実行することです。
インライン ビューを作成し、そこから選択することで制限を回避しようとする 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 id in ($bases_list_expr)
UPDATE
IN
句を使用する代わりにJOIN
、IDを取得している他のテーブルで使用してみてください。そうすれば、制限について心配する必要はありません。私の側からの考えです。
それ以外のSELECT * FROM table1 WHERE ID IN (1,2,3,4,...,1000);
これを使って :
SELECT * FROM table1 WHERE ID IN (SELECT rownum AS ID FROM dual connect BY level <= 1000);
*これが依存関係である場合、ID が他の外部 IDS を参照していないことを確認する必要があることに注意してください。既存の ID のみが使用可能であることを確認するには、次のようにします。
SELECT * FROM table1 WHERE ID IN (SELECT distinct(ID) FROM tablewhereidsareavailable);
乾杯