SQL WHERE ID IN (ID1, ID2,…, IDN)

168

Ich muss eine Abfrage schreiben, um eine große Liste von IDs abzurufen.

Wir unterstützen viele Backends (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), daher muss ich ein Standard-SQL schreiben.

Die Größe des ID-Satzes könnte groß sein, die Abfrage würde programmgesteuert generiert. Was ist der beste Ansatz?

1) Schreiben einer Abfrage mit IN

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

Meine Frage hier ist. Was passiert, wenn n sehr groß ist? Und was ist mit der Leistung?

2) Schreiben einer Abfrage mit ODER

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

Ich denke, dass dieser Ansatz keine n-Grenze hat, aber was ist mit der Leistung, wenn n sehr groß ist?

3) Schreiben einer programmatischen Lösung:

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

Bei diesem Ansatz sind einige Probleme aufgetreten, wenn der Datenbankserver über das Netzwerk abgefragt wird. Normalerweise ist es besser, eine Abfrage durchzuführen, bei der alle Ergebnisse abgerufen werden, als viele kleine Abfragen durchzuführen. Vielleicht bin ich falsch.

Was wäre eine richtige Lösung für dieses Problem?

Daniel Peñalba
quelle
1
Option 1 reduziert die Antwortzeit des SQL Servers erheblich, indem 7.000 IDs ausgewählt werden, von denen einige nicht vorhanden waren. Normalerweise dauerte die Abfrage ungefähr 1300 ms, sie reduziert sich mit IN! Auf 80 ms ! Ich habe meine als Ihre Lösung 1 + 3 gemacht. Nur die letzte Abfrage war eine lange Abfragezeichenfolge, die zur Ausführung an SQL gesendet wurde.
Piotr Kula

Antworten:

107

Option 1 ist die einzig gute Lösung.

Warum?

  • Option 2 macht dasselbe, aber Sie wiederholen den Spaltennamen viele Male. Außerdem weiß die SQL-Engine nicht sofort, dass Sie überprüfen möchten, ob der Wert einer der Werte in einer festen Liste ist. Eine gute SQL-Engine könnte sie jedoch optimieren, um die gleiche Leistung wie bei zu erzielen IN. Es gibt jedoch immer noch ein Problem mit der Lesbarkeit ...

  • Option 3 ist in Bezug auf die Leistung einfach schrecklich. Es sendet jede Abfrage eine Abfrage und hämmert die Datenbank mit kleinen Abfragen. Es verhindert auch, dass Optimierungen für "Wert ist einer der Werte in einer bestimmten Liste" verwendet werden.

DiebMaster
quelle
2
Ich stimme zu, aber beachten Sie, dass die In-Liste in vielen RDMS begrenzt ist und Sie daher die Lösung von @Ed Guiness verwenden müssen, aber hier unterscheiden sich temporäre Tabellen zwischen RDBMS. (Effektiv für komplexe Probleme können Sie nicht nur reines Standard-SQL verwenden)
mmmmmm
28

Ein alternativer Ansatz könnte darin bestehen, eine andere Tabelle zu verwenden, um ID-Werte zu enthalten. Diese andere Tabelle kann dann in Ihrer TABELLE innerlich verbunden werden, um zurückgegebene Zeilen einzuschränken. Dies hat den großen Vorteil, dass Sie kein dynamisches SQL benötigen (im besten Fall problematisch) und keine unendlich lange IN-Klausel haben.

Sie würden diese andere Tabelle abschneiden, Ihre große Anzahl von Zeilen einfügen und dann möglicherweise einen Index erstellen, um die Join-Leistung zu unterstützen. Außerdem können Sie die Anhäufung dieser Zeilen vom Abrufen von Daten trennen und möglicherweise mehr Optionen zum Optimieren der Leistung erhalten.

Update : Obwohl Sie eine temporäre Tabelle verwenden könnten, wollte ich nicht implizieren, dass Sie müssen oder sogar sollten. Eine permanente Tabelle, die für temporäre Daten verwendet wird, ist eine gängige Lösung mit Vorteilen, die über die hier beschriebenen hinausgehen.

Ed Guiness
quelle
1
Aber wie würden Sie die Liste der benötigten IDs übergeben? (Da Sie keinen Bereich oder ähnliches auswählen können).
raam86
1
@ raam86: Die Liste der IDs wurde möglicherweise mithilfe einer selectAnweisung in einer anderen Tabelle abgerufen . Die Liste wird als die andere Tabelle übergeben, inner joingegen die Sie antreten.
Bdforbes
19

Was Ed Guiness vorschlug, ist wirklich ein Leistungssteigerer. Ich hatte eine solche Anfrage

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

Was ich getan habe :

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

Dann verband Inner die Temperatur mit Haupttisch:

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

Und die Leistung hat sich drastisch verbessert.

Ritu
quelle
1
Hallo, ist fnSplitter eine Funktion von MSSQL? Weil ich es nicht finden konnte.
WiiMaxx
Es ist keine Standardsache. Sie müssen bedeuten, dass sie diese Funktion für diesen Zweck geschrieben haben oder z. B. eine Anwendung hatten, die sie bereits bereitgestellt hat.
underscore_d
fnSplitter ist eine von Ritu erstellte Funktion, die Sie im Internet / Google ähnlich finden können
Bashar Abu Shamaa
9

Erste Option ist definitiv die beste Option.

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

Wenn man bedenkt, dass die Liste der IDs sehr groß ist , sagen wir Millionen, sollten Sie die folgenden Blockgrößen berücksichtigen:

  • Teilen Sie Ihre Liste der IDs in Abschnitte mit fester Anzahl, z. B. 100
  • Die Blockgröße sollte basierend auf der Speichergröße Ihres Servers festgelegt werden
  • Angenommen, Sie haben 10000 IDs, dann haben Sie 10000/100 = 100 Chunks
  • Verarbeiten Sie jeweils einen Block, was zu 100 Datenbankaufrufen zur Auswahl führt

Warum sollten Sie sich in Stücke teilen?

Sie werden niemals eine Speicherüberlauf-Ausnahme erhalten, die in Szenarien wie Ihren sehr häufig vorkommt. Sie haben die Anzahl der Datenbankaufrufe optimiert, was zu einer besseren Leistung führt.

Für mich hat es immer wie ein Zauber gewirkt. Hoffe, es würde auch für meine Kollegen funktionieren :)

Adarsh ​​Kumar
quelle
4

Das Ausführen des Befehls SELECT * FROM MyTable where id in () in einer Azure SQL-Tabelle mit 500 Millionen Datensätzen führte zu einer Wartezeit von> 7 Minuten!

Wenn Sie dies stattdessen tun, werden sofort Ergebnisse zurückgegeben:

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

Verwenden Sie einen Join.

JakeJ
quelle
3

In den meisten Datenbanksystemen werden IN (val1, val2, …)eine Reihe von Systemen ORnach demselben Plan optimiert.

Der dritte Weg wäre, die Liste der Werte in eine temporäre Tabelle zu importieren und sie zu verbinden, was in den meisten Systemen effizienter ist, wenn es viele Werte gibt.

Vielleicht möchten Sie diese Artikel lesen:

Quassnoi
quelle
3

Beispiel 3 wäre der schlechteste von allen, da Sie die Datenbank unzählige Male ohne ersichtlichen Grund aufrufen.

Das Laden der Daten in eine temporäre Tabelle und das anschließende Verbinden wäre bei weitem am schnellsten. Danach sollte der IN etwas schneller arbeiten als die Gruppe der OPs.

Judda
quelle
2

Ich denke, Sie meinen SqlServer, aber unter Oracle haben Sie eine feste Grenze, wie viele IN-Elemente Sie angeben können: 1000.

flq
quelle
1
Sogar SQL Server funktioniert nach ~ 40k IN-Elementen nicht mehr. Laut MSDN: Das Einfügen einer extrem großen Anzahl von Werten (viele Tausend) in eine IN-Klausel kann Ressourcen verbrauchen und Fehler 8623 oder 8632 zurückgeben. Um dieses Problem zu umgehen, speichern Sie die Elemente in der IN-Liste in einer Tabelle.
Jahav