web-dev-qa-db-fra.com

Comment mettre plus de 1000 valeurs dans une clause Oracle IN

Est-il possible de contourner la limitation Oracle 10g de 1 000 éléments dans une clause statique IN? J'ai une liste délimitée par des virgules de nombreux ID que je souhaite utiliser dans une clause IN. Parfois, cette liste peut contenir plus de 1 000 éléments. Oracle renvoie alors une erreur. La requête est similaire à celle-ci ...

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

Placez les valeurs dans une table temporaire puis effectuez un select où id dans (select id de tentant)

89
Otávio Décio

Je suis presque sûr que vous pouvez fractionner les valeurs sur plusieurs RI en utilisant OU:

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

Vous pouvez essayer d'utiliser le formulaire suivant:

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

Où obtenez-vous la liste des identifiants en premier lieu? S'ils sont des identifiants dans votre base de données, sont-ils issus d'une requête précédente?

Quand j'ai vu cela dans le passé, c'était parce que: -

  1. une table de référence est manquante et la méthode correcte consiste à ajouter la nouvelle table, à placer un attribut sur cette table et à la rejoindre
  2. une liste d'identifiants est extraite de la base de données, puis utilisée dans une instruction SQL ultérieure (éventuellement ultérieurement, sur un autre serveur ou sur un autre). Dans ce cas, la réponse est de ne jamais l'extraire de la base de données. Stockez dans une table temporaire ou écrivez simplement une requête.

Je pense qu'il y a peut-être de meilleures façons de retravailler ce code en appliquant simplement cette instruction SQL. Si vous fournissez plus de détails, vous aurez peut-être des idées.

8
WW.

Utilisez ... depuis la 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; 
/ 

C'est l'un des rares cas où vous avez besoin d'un indice, sinon Oracle n'utilisera pas l'index sur l'id de la colonne. L'un des avantages de cette approche est qu'Oracle n'a pas besoin d'analyser la requête à plusieurs reprises. L'utilisation d'une table temporaire est la plupart du temps plus lente.

edit 1 simplifié la procédure (merci à jimmyorr) + exemple

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

Exemple:

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

Cela produira:

Raymond
Hans
5
tuinstoel

Je me suis retrouvé ici à la recherche d'une solution.

En fonction du nombre d'éléments haut de gamme sur lesquels vous devez effectuer une requête et en supposant que vos éléments soient uniques, vous pouvez fractionner votre requête en lots de requêtes de 1 000 éléments et combiner les résultats de votre côté (pseudocode ici):

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

Cela peut constituer un bon compromis dans le cas où vous ne disposez généralement pas de plus de 1 000 éléments, car avoir plus de 1 000 éléments constituerait votre scénario "haut de gamme". Par exemple, si vous avez 1 500 éléments, deux requêtes de (1000, 500) ne seraient pas si mauvaises. Cela suppose également que chaque requête n'est pas particulièrement onéreuse.

Ceci ne serait pas serait approprié si votre nombre typique d'éléments attendus devait être beaucoup plus important - disons de l'ordre de 100 000 - nécessitant 100 requêtes. Si tel est le cas, vous devriez probablement vous pencher plus sérieusement sur l’utilisation de la solution globale de tables temporaires fournie ci-dessus en tant que solution la plus "correcte". De plus, si vos articles ne sont pas uniques, vous devrez également résoudre les doublons dans vos lots.

4
Mike Atlas

Oui, situation très étrange pour Oracle.

si vous spécifiez 2000 ID dans la clause IN, la tentative échouera. cela échoue:

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

mais si vous mettez simplement les 2000 ids dans une autre table (table temp par exemple), cela fonctionnera comme suit:

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

ce que vous pouvez faire peut en fait diviser les enregistrements en un grand nombre d’enregistrements et les exécuter groupe par groupe.

1
Aaron He

Au lieu d'utiliser la clause IN, pouvez-vous essayer d'utiliser JOIN avec l'autre table, qui récupère l'identifiant. De cette façon, nous n'avons pas à nous soucier de la limite. juste une pensée de mon côté.

0
Raju

Voici un code Perl qui tente de contourner la limite en créant une vue intégrée, puis en effectuant une sélection. Le texte de l'instruction est compressé en utilisant des lignes de douze éléments au lieu de sélectionner chaque élément individuellement dans DUAL, puis non compressé en unissant toutes les colonnes. UNION ou UNION ALL en décompression ne devrait pas faire de différence ici, car tout se passe dans un IN qui imposera l’unicité avant de le rejoindre, mais dans la compression, UNION ALL est utilisé pour éviter de nombreuses comparaisons inutiles. Comme les données que je filtre sont des nombres entiers, la citation n’est pas un problème.

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

On utiliserait ça comme ça:

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