it-swarm.com.de

So fügen Sie mehr als 1000 Werte in eine Oracle IN-Klausel ein

Gibt es eine Möglichkeit, die Beschränkung von Oracle 10g auf 1000 Elemente in einer statischen IN-Klausel zu umgehen? Ich habe eine durch Kommas getrennte Liste mit vielen IDs, die ich in einer IN-Klausel verwenden möchte. Manchmal kann diese Liste mehr als 1000 Elemente enthalten. Zu diesem Zeitpunkt gibt Oracle einen Fehler aus. Die Abfrage ist ähnlich wie folgt ...

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

Fügen Sie die Werte in eine temporäre Tabelle ein und wählen Sie dann aus, wo die ID eingegeben werden soll (wählen Sie die ID aus "Versuchbar" aus).

89
Otávio Décio

Ich bin mir fast sicher, dass Sie mit OR Werte auf mehrere INs aufteilen können:

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

Sie können versuchen, das folgende Formular zu verwenden:

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

Woher bekommen Sie die Liste der IDs überhaupt? Da es sich um IDs in Ihrer Datenbank handelt, stammen sie aus früheren Abfragen?

Wenn ich das in der Vergangenheit gesehen habe, dann aus folgenden Gründen:

  1. es fehlt eine Referenztabelle, und der richtige Weg besteht darin, die neue Tabelle hinzuzufügen, ein Attribut für diese Tabelle festzulegen und eine Verknüpfung zu dieser zu erstellen
  2. eine Liste von IDs wird aus der Datenbank extrahiert und dann in einer nachfolgenden SQL-Anweisung verwendet (möglicherweise später oder auf einem anderen Server oder was auch immer). In diesem Fall lautet die Antwort, sie niemals aus der Datenbank zu extrahieren. Entweder in einer temporären Tabelle speichern oder nur eine Abfrage schreiben.

Ich denke, es gibt vielleicht bessere Möglichkeiten, diesen Code zu überarbeiten, als nur diese SQL-Anweisung zum Laufen zu bringen. Wenn Sie weitere Details angeben, erhalten Sie möglicherweise einige Ideen.

8
WW.

Verwenden Sie ... vom Tisch (...:

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; 
/ 

Dies ist einer der seltenen Fälle, in denen Sie einen Hinweis benötigen, da Oracle sonst den Index für die Spalten-ID nicht verwendet. Einer der Vorteile dieses Ansatzes ist, dass Oracle die Abfrage nicht immer wieder analysieren muss. Die Verwendung einer temporären Tabelle ist meistens langsamer.

edit 1 vereinfachte die Prozedur (dank jimmyorr) + Beispiel

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;
/

Beispiel:

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;
/

Dies wird Folgendes ausgeben:

Raymond
Hans
5
tuinstoel

Ich bin auch hier auf der Suche nach einer Lösung gelandet.

Abhängig von der High-End-Anzahl der abzufragenden Elemente und der Annahme, dass Ihre Elemente eindeutig sind, können Sie Ihre Abfrage in Stapelabfragen mit 1000 Elementen aufteilen und stattdessen die Ergebnisse an Ihrem Ende kombinieren (Pseudocode hier):

//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));
}

Dies kann ein guter Kompromiss in einem Szenario sein, in dem normalerweise nicht mehr als 1000 Artikel vorhanden sind - da über 1000 Artikel Ihr "High-End" Edge-Case-Szenario wären. Wenn Sie beispielsweise 1500 Elemente haben, wären zwei Abfragen von (1000, 500) nicht so schlecht. Dies setzt auch voraus, dass jede Abfrage für sich genommen nicht besonders teuer ist.

Dies würde nicht ist angemessen, wenn Ihre typische Anzahl erwarteter Elemente viel größer werden muss - beispielsweise im Bereich 100000 - und 100 Abfragen erforderlich sind. In diesem Fall sollten Sie sich wahrscheinlich eingehender mit der Verwendung der oben bereitgestellten globalen temporären Tabellenlösung als der "korrektesten" Lösung befassen. Wenn Ihre Artikel nicht eindeutig sind, müssen Sie außerdem doppelte Ergebnisse in Ihren Chargen auflösen.

4
Mike Atlas

Ja, sehr seltsame Situation für Oracle.

wenn Sie in der IN-Klausel 2000 IDs angeben, schlägt dies fehl. das schlägt fehl:

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

aber wenn Sie einfach die 2000-IDs in eine andere Tabelle (z. B. temporäre Tabelle) einfügen, funktioniert Folgendes:

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

was Sie tun können, könnte tatsächlich die Datensätze in eine Menge von 1000 Datensätzen aufteilen und sie Gruppe für Gruppe ausführen.

1
Aaron He

Anstatt die IN -Klausel zu verwenden, können Sie versuchen, JOIN mit der anderen Tabelle zu verwenden, die die ID abruft. Auf diese Weise brauchen wir uns keine Sorgen um Grenzen zu machen. nur ein gedanke von meiner seite.

0
Raju

Hier ist etwas Perl-Code, der versucht, das Limit zu umgehen, indem er eine Inline-Ansicht erstellt und diese dann auswählt. Der Anweisungstext wird komprimiert, indem Zeilen mit jeweils zwölf Elementen verwendet werden, anstatt jedes Element einzeln aus DUAL auszuwählen, und anschließend dekomprimiert, indem alle Spalten zusammengefasst werden. UNION oder UNION ALL bei der Dekomprimierung sollten hier keinen Unterschied machen, da alles in einem IN abläuft, das ohnehin eine Eindeutigkeit auferlegt, bevor es dagegen gefügt wird. Bei der Komprimierung wird UNION ALL verwendet, um viele unnötige Vergleiche zu vermeiden. Da es sich bei den Daten, nach denen ich filtere, um ganze Zahlen handelt, ist das Zitieren kein Problem.

#
# 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
}

Das würde man so benutzen:

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