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

170

Muszę napisać zapytanie, aby pobrać dużą listę identyfikatorów.

Obsługujemy wiele backendów (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), więc muszę napisać standardowy SQL.

Rozmiar zestawu id mógłby być duży, zapytanie byłoby generowane programowo. Więc jakie jest najlepsze podejście?

1) Pisanie zapytania za pomocą IN

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

Moje pytanie brzmi. Co się stanie, jeśli n jest bardzo duże? A co z wydajnością?

2) Pisanie zapytania przy użyciu OR

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

Myślę, że to podejście nie ma limitu n, ale co z wydajnością, jeśli n jest bardzo duże?

3) Pisanie rozwiązania programistycznego:

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

Wystąpiły pewne problemy z tym podejściem, gdy serwer bazy danych był odpytywany przez sieć. Zwykle lepiej jest wykonać jedno zapytanie, które spowoduje pobranie wszystkich wyników, niż wykonanie wielu małych zapytań. Może się mylę.

Jakie byłoby prawidłowe rozwiązanie tego problemu?

Daniel Peñalba
źródło
1
Opcja 1 znacznie skraca czas odpowiedzi serwera SQL, wybierając 7k ID, których część nie istniała. Zwykle zapytanie trwało około 1300 ms, przy użyciu IN! Zrobiłem swoje jako twoje rozwiązanie 1 + 3. Tylko ostatnie zapytanie było jednym, długim ciągiem zapytania wysłanym do SQL w celu wykonania.
Piotr Kula

Odpowiedzi:

108

Opcja 1 to jedyne dobre rozwiązanie.

Czemu?

  • Opcja 2 robi to samo, ale powtarzasz nazwę kolumny wiele razy; dodatkowo silnik SQL nie wie od razu, że chcesz sprawdzić, czy wartość jest jedną z wartości na ustalonej liście. Jednak dobry silnik SQL może go zoptymalizować, aby uzyskać taką samą wydajność jak w przypadku IN. Nadal jednak występuje problem z czytelnością ...

  • Opcja 3 jest po prostu okropna pod względem wydajności. Wysyła zapytanie w każdej pętli i wbija bazę danych małymi zapytaniami. Zapobiega również stosowaniu jakichkolwiek optymalizacji dla „wartość jest jedną z tych na danej liście”

ThiefMaster
źródło
2
Zgadzam się, ale zauważ, że lista ta jest ograniczona w wielu RDMS, więc będziesz potrzebował nas użyć rozwiązania @Ed Guiness, ale tutaj tabele tymczasowe różnią się między RDBMS. (Skutecznie w przypadku złożonych problemów nie można używać tylko czystego standardowego SQL)
mmmmmm
28

Alternatywnym podejściem może być użycie innej tabeli do przechowywania wartości id. Tę inną tabelę można następnie połączyć wewnętrznie w tabeli, aby ograniczyć zwracane wiersze. Będzie to miało tę główną zaletę, że nie będziesz potrzebować dynamicznego SQL (co jest problematyczne w najlepszym przypadku) i nie będziesz mieć nieskończenie długiej klauzuli IN.

Możesz obciąć tę drugą tabelę, wstawić dużą liczbę wierszy, a następnie prawdopodobnie utworzyć indeks, aby wspomóc wydajność łączenia. Pozwoliłoby to również oddzielić gromadzenie tych wierszy od pobierania danych, być może dając więcej opcji dostrajania wydajności.

Aktualizacja : Chociaż możesz użyć tabeli tymczasowej, nie chciałem sugerować, że musisz, a nawet powinieneś. Stała tabela używana do tymczasowych danych jest typowym rozwiązaniem, którego zalety wykraczają poza opisane tutaj.

Ed Guiness
źródło
1
Ale w jaki sposób możesz przekazać listę potrzebnych identyfikatorów? (Widząc, że nie możesz wybrać zakresu ani czegoś takiego).
raam86
1
@ raam86: lista identyfikatorów mogła zostać uzyskana przy użyciu selectinstrukcji z innej tabeli. Lista jest przekazywana jako druga tabela, inner joinprzeciwko której jesteś .
bdforbes
19

To, co zasugerował Ed Guiness, jest naprawdę poprawiające wydajność, miałem takie pytanie

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

co ja zrobiłem :

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

Następnie wewnętrzna połączyła temp z głównym stołem:

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

Wydajność poprawiła się drastycznie.

Ritu
źródło
1
Cześć, czy fnSplitter jest funkcją z MSSQL? Ponieważ nie mogłem go znaleźć.
WiiMaxx
To nie jest standardowa rzecz. Muszą oznaczać, że napisali tę funkcję w tym celu lub np. Mieli aplikację, która już ją udostępniła.
underscore_d
fnSplitter to funkcja stworzona przez Ritu, podobną do niej można znaleźć w internecie / google
Bashar Abu Shamaa
9

Pierwsza opcja to zdecydowanie najlepsza opcja.

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

Biorąc jednak pod uwagę, że lista identyfikatorów jest bardzo duża , powiedzmy miliony, należy wziąć pod uwagę rozmiary fragmentów, jak poniżej:

  • Podziel listę identyfikatorów na części o ustalonej liczbie, powiedzmy 100
  • Rozmiar fragmentu powinien zostać określony na podstawie rozmiaru pamięci serwera
  • Załóżmy, że masz 10000 identyfikatorów, będziesz mieć 10000/100 = 100 kawałków
  • Przetwarzaj jedną porcję na raz, co spowoduje 100 wywołań bazy danych dla funkcji select

Dlaczego należy dzielić na kawałki?

Nigdy nie otrzymasz wyjątku przepełnienia pamięci, który jest bardzo powszechny w scenariuszach takich jak twój. Będziesz mieć zoptymalizowaną liczbę wywołań bazy danych, co skutkuje lepszą wydajnością.

Zawsze działało na mnie jak urok. Mam nadzieję, że zadziała również dla moich kolegów programistów :)

Adarsh ​​Kumar
źródło
4

Wykonanie polecenia SELECT * FROM MyTable where id in () na tabeli Azure SQL z 500 milionami rekordów skutkowało czasem oczekiwania> 7 minut!

Zamiast tego natychmiast zwróciło wyniki:

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

Użyj złączenia.

JakeJ
źródło
3

W większości systemów baz danych IN (val1, val2, …)i serii ORsą zoptymalizowane do tego samego planu.

Trzecim sposobem byłoby zaimportowanie listy wartości do tabeli tymczasowej i dołączenie do niej, co jest bardziej wydajne w większości systemów, jeśli jest dużo wartości.

Możesz przeczytać te artykuły:

Quassnoi
źródło
3

Próbka 3 byłaby najgorsza ze wszystkich, ponieważ odwiedzasz bazę danych niezliczoną ilość razy bez wyraźnego powodu.

Załadowanie danych do tabeli tymczasowej, a następnie dołączenie do tego byłoby zdecydowanie najszybsze. Potem IN powinno działać nieco szybciej niż grupa OR.

judda
źródło
2

Myślę, że masz na myśli SqlServer, ale w Oracle masz sztywny limit liczby elementów IN, które możesz określić: 1000.

flq
źródło
1
Nawet SQL Server przestaje działać po ~ 40k IN elementów. Według MSDN: zawarcie bardzo dużej liczby wartości (wiele tysięcy) w klauzuli IN może spowodować zużycie zasobów i zwrócenie błędów 8623 lub 8632. Aby obejść ten problem, należy przechowywać elementy z listy IN w tabeli.
jahav