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

170

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

Nous prenons en charge de nombreux backends (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...) donc j'ai besoin d'écrire un SQL standard.

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

1) Ecrire une requête en utilisant IN

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

Ma question est ici. Que se passe-t-il si n est très grand? Et qu'en est-il des performances?

2) Ecrire 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 des performances si n est très grand?

3) Rédaction d'une solution programmatique:

  foreach (var id in myIdList)
  {
      var 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 seule requête qui récupère tous les résultats plutôt que de faire beaucoup de petites requêtes. J'ai peut-être tort.

Quelle serait la bonne solution à ce problème?

Daniel Peñalba
la source
1
L'option 1 réduit considérablement le temps de réponse du serveur SQL, en sélectionnant 7k ID, dont certains n'existaient pas. Normalement, la requête a pris environ 1300 ms, elle se réduit à 80 ms en utilisant IN! J'ai fait le mien comme votre solution 1 + 3. Juste la requête finale était une longue chaîne de requête envoyée à SQL pour s'exécuter.
Piotr Kula

Réponses:

108

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 que 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 avoir des performances égales comme avec IN. Il y a cependant toujours un problème de lisibilité ...

  • 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 les optimisations pour "la valeur est l'une de celles d'une liste donnée"

ThiefMaster
la source
2
Je suis d'accord mais notez que la liste in est limitée dans de nombreux SGBDR et que vous auriez donc besoin que nous utilisions la solution @Ed Guiness, mais ici les tables temporaires diffèrent entre les SGBDR. (En fait, pour les problèmes complexes, vous ne pouvez pas utiliser uniquement du SQL standard pur)
mmmmmm
28

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

Vous pourriez tronquer cette autre table, insérer votre grand nombre de lignes, puis peut-être créer un index pour faciliter les performances de jointure. Cela vous permettrait également de dissocier l'accumulation de ces lignes de la récupération des données, vous donnant peut-être plus d'options pour régler les performances.

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

Ed Guiness
la source
1
Mais comment passeriez-vous la liste des identifiants dont vous avez besoin? (Vu que vous ne pouvez pas sélectionner une plage ou quelque chose comme ça).
raam86
1
@ raam86: la liste des identifiants peut avoir été obtenue en utilisant une selectinstruction sur une autre table. La liste est transmise comme l'autre table à laquelle vous vous inner joinopposez.
bdforbes
19

Ce que Ed Guiness a suggéré est vraiment un booster de performances, 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#')

Ensuite, l'intérieur a rejoint la température 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.

Ritu
la source
1
Salut, fnSplitter est-il une fonction de MSSQL? Parce que je n'ai pas pu le trouver.
WiiMaxx
Ce n'est pas une chose standard. Ils doivent signifier qu'ils ont écrit cette fonction à cette fin, ou par exemple qu'ils avaient une application qui la fournissait déjà.
underscore_d
fnSplitter est une fonction créée par Ritu, vous pouvez trouver sur internet / google similaire de celui
Bashar Abu Shamaa
9

La première option est certainement la meilleure option.

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

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

  • Divisez votre liste d'identifiants en morceaux de nombre fixe, disons 100
  • La taille du bloc doit être déterminée en fonction de la taille de la mémoire de votre serveur
  • Supposons que vous ayez 10000 identifiants, vous aurez 10000/100 = 100 morceaux
  • Traitez un morceau à la fois, ce qui entraîne 100 appels à la base de données pour sélectionner

Pourquoi devriez-vous diviser en morceaux?

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

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

Adarsh ​​Kumar
la source
4

L'exécution de la commande SELECT * FROM MyTable où id in () sur une table Azure SQL avec 500 millions d'enregistrements a entraîné un temps d'attente> 7min!

Faire cela à la place a renvoyé des résultats immédiatement:

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

Utilisez une jointure.

JakeJ
la source
3

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

La troisième façon serait d'importer la liste de valeurs dans une table temporaire et de la joindre, ce qui est plus efficace dans la plupart des systèmes, s'il y a beaucoup de valeurs.

Vous voudrez peut-être lire ces articles:

Quassnoi
la source
3

L'échantillon 3 serait le moins performant de tous, car vous consultez 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 IN devrait fonctionner un peu plus vite que le groupe des OR.

Judda
la source
2

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

flq
la source
1
Même SQL Server cesse de fonctionner après ~ 40 000 éléments IN. Selon MSDN: inclure un très grand nombre de valeurs (plusieurs milliers) dans une clause IN peut consommer des ressources et renvoyer des erreurs 8623 ou 8632. Pour contourner ce problème, stockez les éléments de la liste IN dans une table.
jahav le