web-dev-qa-db-fra.com

Existe-t-il une solution de contournement pour ORA-01795: le nombre maximal d'expressions dans une liste est une erreur 1000?

Y at-il une solution de contournement pour

'ORA-01795: maximum number of expressions in a list is 1000 error'

J'ai une requête et il sélectionne des champs en fonction de la valeur d'un champ. J'utilise la clause in et il y a plus de 10000 valeurs

exemple:

select field1, field2, field3 
from table1 
where name in 
(
'value1',
'value2',
...
'value10000+'
);

Chaque fois que j'exécute la requête, je reçois le ORA-01795: maximum number of expressions in a list is 1000 error. J'essaie d'exécuter la requête dans TOAD, pas de différence, la même erreur. Comment pourrais-je modifier la requête pour la faire fonctionner?

Merci d'avance

62
valmont74

Utilisez simplement plusieurs clauses dans pour contourner ce problème:

select field1, field2, field3 from table1 
where  name in ('value1', 'value2', ..., 'value999') 
    or name in ('value1000', ..., 'value1999') 
    or ...;
104
Fabian Barney

J'ai rencontré ce problème récemment et j'ai trouvé un moyen effronté de le faire sans regrouper les clauses IN supplémentaires.

Tu pourrais utiliser Tuples

SELECT field1, field2, field3
FROM table1
WHERE (1, name) IN ((1, value1), (1, value2), (1, value3),.....(1, value5000));

Oracle autorise> 1 000 Tuples, mais pas les valeurs simples. Plus à ce sujet ici,

https://community.Oracle.com/message/3515498#3515498
et
https://community.Oracle.com/thread/958612

C'est bien sûr si vous n'avez pas la possibilité d'utiliser une sous-requête dans IN pour obtenir les valeurs dont vous avez besoin à partir d'une table temporaire.

22
MangoCrysis

Certaines solutions de contournement sont:

1- Diviser une clause IN en plusieurs clauses IN dont le nombre est inférieur à 1000 et les combiner à l’aide de OR:

Fractionner la clause "WHERE" d'origine d'une condition "IN" en plusieurs conditions "IN":

Select id from x where id in (1, 2, ..., 1000,…,1500);

À:

Select id from x where id in (1, 2, ..., 999) OR id in (1000,...,1500);

2- Utilisation des multiplets: La limite de 1 000 s'applique aux ensembles d'éléments uniques: (x) IN ((1), (2), (3), ...). Il n'y a pas de limite si les ensembles contiennent deux ou plusieurs éléments: (x, 0) IN ((1,0), (2,0), (3,0), ...):

Select id from x where (x.id, 0) IN ((1, 0), (2, 0), (3, 0),.....(n, 0));

3- Utilisation de la table temporaire:

Select id from x where id in (select id from <temporary-table>);
18
Ahmed MANSOUR

Une autre façon:

CREATE OR REPLACE TYPE TYPE_TABLE_OF_VARCHAR2 AS TABLE OF VARCHAR(100);
-- ...
SELECT field1, field2, field3
  FROM table1
  WHERE name IN (
    SELECT * FROM table (SELECT CAST(? AS TYPE_TABLE_OF_VARCHAR2) FROM dual)
  );

Je ne considère pas que c'est optimal, mais ça marche. Le conseil /*+ CARDINALITY(...) */ serait très utile car Oracle ne comprend pas la cardinalité du tableau transmis et ne peut pas estimer le plan d'exécution optimal.

Autre alternative: insertion par lots dans la table temporaire et utilisation de la dernière sous-requête pour le prédicat IN.

7
svaor

Veuillez utiliser une requête interne à l'intérieur de la clause in-:

select col1, col2, col3... from table1
 where id in (select id from table2 where conditions...)
7
Vikas Kumar

il existe également un autre moyen de résoudre ce problème. Disons que vous avez deux tables Table1 et Table2. et il est nécessaire d'extraire toutes les entrées de Table1 non référencées/présentes dans la Table2 à l'aide de la requête de critères. Alors allez-y comme ça ...

List list=new ArrayList(); 
Criteria cr=session.createCriteria(Table1.class);
cr.add(Restrictions.sqlRestriction("this_.id not in (select t2.t1_id from Table2 t2 )"));
.
.

. . . Il effectuera toute la fonction de sous-requête directement dans SQL sans inclure 1000 paramètres ou plus dans SQL convertis par le framework Hibernate. Cela a fonctionné pour moi. Remarque: Vous devrez peut-être modifier la partie SQL selon vos besoins.

3
Fahim Ashraf

Il existe une autre option: with syntaxe. Pour utiliser l'exemple des PO, ceci ressemblerait à ceci:

with data as (
  select 'value1' name from dual
  union all
  select 'value2' name from dual
  union all
...
  select 'value10000+' name from dual)
select field1, field2, field3 
from table1 t1
inner join data on t1.name = data.name;

J'ai rencontré ce problème. Dans mon cas, j'avais une liste de données dans Java où chaque article avait un item_id et un customer_id. J'ai deux tables dans la base de données avec des abonnements aux articles des clients respectifs. Je veux obtenir une liste de tous les abonnements aux articles ou au client pour cet article, ainsi que l'identifiant de l'article.

J'ai essayé trois variantes:

  1. Plusieurs sélections à partir de Java (en utilisant des n-uplets pour contourner la limite)
  2. Avec syntaxe
  3. Table temporaire

Option 1: Choix multiples à partir de Java

Fondamentalement, j'ai d'abord

select item_id, token 
from item_subs 
where (item_id, 0) in ((:item_id_0, 0)...(:item_id_n, 0))

Ensuite

select cus_id, token 
from cus_subs 
where (cus_id, 0) in ((:cus_id_0, 0)...(:cus_id_n, 0))

Ensuite, je construis une carte dans Java avec le cus_id en tant que clé et une liste d'éléments en tant que valeur, et pour chaque abonnement client trouvé, j'ajoute (à la liste renvoyée par la première sélection) une entrée pour tous les éléments pertinents avec cet item_id. C'est un code beaucoup plus compliqué

Option 2: Avec syntaxe

Obtenez tout en une fois avec un SQL comme

with data as (
  select :item_id_0 item_id, :cus_id_0 cus_id
  union all
  ...
  select :item_id_n item_id, :cus_id_n cus_id )
select I.item_id item_id, I.token token
from item_subs I
inner join data D on I.item_id = D.item_id
union all
select D.item_id item_id, C.token token
from cus_subs C
inner join data D on C.cus_id = D.cus_id

Option 3: Table temporaire

Créez une table temporaire globale avec trois champs: rownr (clé primaire), item_id et cus_id. Insérez toutes les données à cet endroit, puis exécutez une sélection très similaire à l'option 2, mais en liant la table temporaire à la place du with data

Performance

Ceci ( n'est pas une analyse de performance entièrement scientifique.

  • J'utilise une base de données de développement, avec un peu plus de 1000 lignes dans mon ensemble de données pour lesquelles je souhaite rechercher des abonnements.
  • J'ai seulement essayé un ensemble de données.
  • Je ne suis pas au même emplacement physique que mon serveur de base de données. Ce n'est pas si loin, mais je remarque que si j'essaye de la maison sur le VPN, alors c'est beaucoup plus lent, même si c'est la même distance (et ce n'est pas mon internet à la maison qui pose problème).
  • J'étais en train de tester l'appel complet. Mon API en appelle donc un autre (s'exécutant également dans la même instance de dev), qui se connecte également à la base de données pour obtenir le jeu de données initial. Mais c'est la même chose dans les trois cas.

YMMV.

Cela dit, l'option de la table temporaire était beaucoup plus lente . Comme en double si lent. Je recevais 14-15 secondes pour l’option 1, 15-16 pour l’option 2 et 30 pour l’option 3.

Je vais les essayer à nouveau sur le même réseau que le serveur de base de données et vérifier si cela change les choses lorsque j'en ai l'occasion.

3
Adam

Syndicat des opérateurs

select * from tableA where tableA.Field1 in (1,2,...999)
union
select * from tableA where tableA.Field1 in (1000,1001,...1999)
union
select * from tableA where tableA.Field1 in (2000,2001,...2999)
2
Nikolai Nechai

Je me rends compte que c’est une vieille question qui fait référence à TOAD, mais si vous avez besoin de coder cela à l’aide de c #, vous pouvez scinder la liste en une boucle for. Vous pouvez essentiellement faire la même chose avec Java en utilisant subList ();

    List<Address> allAddresses = GetAllAddresses();
    List<Employee> employees = GetAllEmployees(); // count > 1000

    List<Address> addresses = new List<Address>();

    for (int i = 0; i < employees.Count; i += 1000)
    {
        int count = ((employees.Count - i) < 1000) ? (employees.Count - i) - 1 : 1000;
        var query = (from address in allAddresses
                     where employees.GetRange(i, count).Contains(address.EmployeeId)
                     && address.State == "UT"
                     select address).ToList();

        addresses.AddRange(query);
    }

J'espère que ça aide quelqu'un.

2
Tyler

Il existe également une solution de contournement de votre tableau, qui a fonctionné pour moi, car d'autres solutions étaient difficiles à mettre en œuvre avec un ancien framework.

select * from tableA where id = 1 or id = 2 or id = 3 ...

Mais pour une meilleure performance, j'utiliserais la solution de Nikolai Nechai avec les syndicats, si possible.

0
El El