web-dev-qa-db-fra.com

SQL WHERE ID IN (id1, id2, ..., idn)

J'ai besoin d'écrire une requête pour récupérer une grande liste d'identifiants.

Nous supportons de nombreux backends (MySQL, Firebird, SQL Server, Oracle, PostgreSQL ...), je dois donc écrire un SQL standard.

La taille de l'ensemble d'identifiants pourrait être grande, la requête serait générée par programme. Alors, quelle est la meilleure approche?

1) Écrire une requête en utilisant IN

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Ma question est la suivante. Que se passe-t-il si n est très grand? Aussi, qu'en est-il de la performance?

2) Écrire une requête en utilisant OU

SELECT * FROM TABLE WHERE ID = id1 OR ID = id2 OR ... OR ID = idn

Je pense que cette approche n’a pas de limite n, mais qu’en est-il de la performance si n est très grand?

3) Rédaction d'une solution programmatique:

  foreach (id in myIdList)
  {
      item = GetItemByQuery("SELECT * FROM TABLE WHERE ID = " + id);
      myObjectList.Add(item);
  }

Nous avons rencontré des problèmes avec cette approche lorsque le serveur de base de données est interrogé sur le réseau. Normalement, il est préférable de faire une requête qui récupère tous les résultats, mieux que beaucoup de petites requêtes. J'ai peut-être tort.

Quelle serait la solution correcte à ce problème?

139
Daniel Peñalba

L'option 1 est la seule bonne solution.

Pourquoi?

  • L'option 2 fait la même chose mais vous répétez le nom de la colonne plusieurs fois. En outre, le moteur SQL ne sait pas immédiatement si vous souhaitez vérifier si la valeur est l'une des valeurs d'une liste fixe. Cependant, un bon moteur SQL pourrait l'optimiser pour obtenir des performances égales, comme avec IN. Il y a toujours le problème de lisibilité cependant ...

  • L'option 3 est tout simplement horrible en termes de performances. Il envoie une requête à chaque boucle et martèle la base de données avec de petites requêtes. Cela l'empêche également d'utiliser des optimisations pour "la valeur est l'une de celles d'une liste donnée"

90
ThiefMaster

Une autre approche pourrait consister à utiliser une autre table pour contenir des valeurs id. Cette autre table peut ensuite être jointe en interne sur votre TABLE pour contraindre les lignes renvoyées. Cela aura l’avantage majeur de ne pas avoir besoin de SQL dynamique (problématique dans le meilleur des cas) et d’avoir une clause IN infiniment longue.

Vous pouvez tronquer cette autre table, insérer votre grand nombre de lignes, puis éventuellement créer un index pour améliorer les performances de la jointure. Cela vous permettrait également de dissocier l'accumulation de ces lignes de la récupération des données, ce qui vous donnerait peut-être plus d'options pour optimiser les performances.

Mise à jour: Bien que vous puissiez utiliser une table temporaire, je ne voulais pas dire que vous deviez ou même devriez. Une table permanente utilisée pour les données temporaires est une solution courante dont les avantages vont au-delà de ceux décrits ici.

23
Ed Guiness

Ce que Ed Guiness a suggéré est vraiment un booster de performance, j'avais une requête comme celle-ci

select * from table where id in (id1,id2.........long list)

ce que j'ai fait :

                            DECLARE @temp table(
                                        ID  int
                                        )
                            insert into @temp 
                            select * from dbo.fnSplitter('#idlist#')

Puis interne rejoint le temp avec la table principale:

select * from table inner join temp on temp.id = table.id

Et les performances se sont considérablement améliorées.

11
Ritu

La première option est définitivement la meilleure option.

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Cependant étant donné que la liste des identifiants est très longue, disons des millions, vous devriez considérer les tailles de morceaux comme ci-dessous:

  • Divisez votre liste d'identifiants en morceaux de nombre fixe, disons 100
  • La taille des morceaux doit être déterminée en fonction de la taille de la mémoire de votre serveur.
  • Supposons que vous ayez 10000 ID, vous aurez 10000/100 = 100 morceaux
  • Traiter un bloc à la fois, ce qui donne 100 appels à la base de données pour

Pourquoi devriez-vous diviser en morceaux?

Vous n'obtiendrez jamais d'exception de dépassement de capacité de mémoire, ce qui est très courant dans des scénarios comme le vôtre. Vous aurez optimisé le nombre d'appels à la base de données pour améliorer les performances.

Cela a toujours fonctionné comme un charme pour moi. J'espère que cela fonctionnera aussi pour mes collègues développeurs :)

7
Adarsh Kumar

L'exécution de la commande SELECT * FROM MyTable où la commande id in () sur une table SQL Azure contenant 500 millions d'enregistrements entraînait un temps d'attente supérieur à 7 minutes!

En faisant cela, les résultats sont immédiatement retournés:

select b.id, a.* from MyTable a
join (values (250000), (2500001), (2600000)) as b(id)
ON a.id = b.id

Utilisez une jointure.

4
JakeJ

L'échantillon 3 serait le moins performant, car vous frappez la base de données d'innombrables fois sans raison apparente.

Charger les données dans une table temporaire puis les rejoindre serait de loin le plus rapide. Après cela, le RI devrait fonctionner légèrement plus vite que le groupe des RUP.

3
judda

Dans la plupart des systèmes de base de données, IN (val1, val2, …) et une série de OR sont optimisés pour le même plan.

La troisième méthode consiste à importer la liste de valeurs dans une table temporaire et à la joindre, ce qui est plus efficace dans la plupart des systèmes, s'il existe de nombreuses valeurs.

Vous voudrez peut-être lire ces articles:

3
Quassnoi

Je pense que vous voulez dire SqlServer mais sur Oracle, vous avez une limite stricte au nombre d'éléments IN que vous pouvez spécifier: 1000.

2
flq