Porządkowanie według kolejności wartości w klauzuli SQL IN ()


154

Zastanawiam się, czy istnieje daleko (prawdopodobnie lepszy sposób) uporządkowania według kolejności wartości w klauzuli IN ().

Problem w tym, że mam 2 zapytania, jedno, które pobiera wszystkie identyfikatory, a drugie, które pobiera wszystkie informacje. Pierwsza tworzy kolejność identyfikatorów, według której chcę, aby druga sortowała. Identyfikatory są umieszczane w klauzuli IN () we właściwej kolejności.

Więc byłoby coś takiego (bardzo uproszczone):

SELECT id FROM table1 WHERE ... ORDER BY display_order, name

SELECT name, description, ... WHERE id IN ([id's from first])

Problem polega na tym, że drugie zapytanie nie zwraca wyników w tej samej kolejności, w jakiej identyfikatory są umieszczane w klauzuli IN ().

Jednym z rozwiązań, które znalazłem, jest umieszczenie wszystkich identyfikatorów w tabeli tymczasowej z polem automatycznie zwiększającym się, które jest następnie łączone z drugim zapytaniem.

Czy jest lepsza opcja?

Uwaga: Ponieważ pierwsze zapytanie jest uruchamiane „przez użytkownika”, a drugie w procesie w tle, nie ma możliwości połączenia zapytania 2 w 1 za pomocą zapytań podrzędnych.

Używam MySQL, ale myślę, że warto byłoby, gdyby zauważył, jakie opcje są dostępne również dla innych baz danych.

Odpowiedzi:


186

Użyj FIELD()funkcji MySQL :

SELECT name, description, ...
FROM ...
WHERE id IN([ids, any order])
ORDER BY FIELD(id, [ids in order])

FIELD() zwróci indeks pierwszego parametru, który jest równy pierwszemu parametrowi (inny niż sam pierwszy parametr).

FIELD('a', 'a', 'b', 'c')

zwróci 1

FIELD('a', 'c', 'b', 'a')

zwróci 3

Zrobi to dokładnie to, co chcesz, jeśli wkleisz identyfikatory do IN()klauzuli i FIELD()funkcji w tej samej kolejności.


3
Sortowanie @Vojto według dowolnej kolejności jest z definicji powolne. Robi to najlepiej, ponieważ nie ma gwarancji, że INi FIELDparametry są równe. Wykonanie tego w kodzie programu może być szybsze dzięki wykorzystaniu tej dodatkowej wiedzy. Oczywiście rozsądniej byłoby przełożyć to obciążenie na klienta, a nie na serwer, jeśli myślisz o wydajności serwera.
ivan_pozdeev,

1
o czym sqlite?
fnc12

15

Zobacz, jak uzyskać posortowane dane.

SELECT ...
  FROM ...
 WHERE zip IN (91709,92886,92807,...,91356)
   AND user.status=1
ORDER 
    BY provider.package_id DESC 
     , FIELD(zip,91709,92886,92807,...,91356)
LIMIT 10

11

Dwa rozwiązania, które przychodzą na myśl:

  1. order by case id when 123 then 1 when 456 then 2 else null end asc

  2. order by instr(','||id||',',',123,456,') asc

( instr()Jest z Oracle, może masz locate()lub charindex()czy coś takiego)


W przypadku MySQL można również użyć FIELD_IN_SET (): dev.mysql.com/doc/refman/5.0/en/…
Darryl Hein


6

Jeśli chcesz wykonać dowolne sortowanie zapytania przy użyciu wartości wprowadzonych przez zapytanie w MS SQL Server 2008+ , możesz to zrobić, tworząc w locie tabelę i wykonując takie łączenie (używając nomenklatury z OP).

SELECT table1.name, table1.description ... 
FROM (VALUES (id1,1), (id2,2), (id3,3) ...) AS orderTbl(orderKey, orderIdx) 
LEFT JOIN table1 ON orderTbl.orderKey=table1.id
ORDER BY orderTbl.orderIdx

Jeśli zamienisz instrukcję VALUES na coś innego, co robi to samo, ale w języku ANSI SQL, powinno to działać na każdej bazie danych SQL.

Uwaga: Druga kolumna w utworzonej tabeli (orderTbl.orderIdx) jest niezbędna podczas odpytywania zestawów rekordów większych niż 100 lub więcej. Początkowo nie miałem kolumny orderIdx, ale stwierdziłem, że przy zestawach wyników większych niż 100 musiałem jawnie sortować według tej kolumny; w SQL Server Express 2014 tak czy inaczej.


To pytanie dotyczy MySQL ... nie jestem pewien, czy twoje zapytanie zadziała w MySQL.
Darryl Hein

2
@Darryl, nie byłoby. Ale ten post pojawia się jako najlepsze wyniki, gdy wyszukujesz w Google, jak zamawiać według klauzuli IN, więc pomyślałem, że równie dobrze mogę opublikować go tutaj, a ogólna metodologia ma zastosowanie do wszystkich serwerów SQL zgodnych z ANSI (po prostu zastąp instrukcję VALUES standardem sposób na stworzenie tabeli).
Ian

Uwaga, ten pracował dla mnie, ale dopiero po tym, jak zastąpić odniesienia do orderTbl.orderKey, orderTbl.orderIndexprzez orderKey,orderIndex
Piotr

5
SELECT ORDER_NO, DELIVERY_ADDRESS 
from IFSAPP.PURCHASE_ORDER_TAB 
where ORDER_NO in ('52000077','52000079','52000167','52000297','52000204','52000409','52000126') 
ORDER BY instr('52000077,52000079,52000167,52000297,52000204,52000409,52000126',ORDER_NO)

działał naprawdę świetnie


Pracował dla mnie w Oracle. Szybko i łatwo. Dzięki.
Menachem

4

Klauzula IN opisuje zbiór wartości, a zbiory nie mają porządku.

Twoje rozwiązanie z łączeniem, a następnie zamawianiem na display_orderkolumnie jest najbardziej prawie poprawnym rozwiązaniem; cokolwiek innego jest prawdopodobnie hackiem specyficznym dla DBMS (lub robi coś z funkcjami OLAP w standardowym SQL). Z pewnością łączenie jest najbardziej przenośnym rozwiązaniem (chociaż generowanie danych z display_orderwartościami może być problematyczne). Zwróć uwagę, że może być konieczne wybranie kolumn kolejności; kiedyś było to wymaganie w standardowym SQL, chociaż uważam, że z reguły było to nieco złagodzone jakiś czas temu (może tak dawno temu jak SQL-92).


2

W przypadku Oracle działa rozwiązanie Johna wykorzystujące funkcję instr (). Oto nieco inne rozwiązanie, które zadziałało - SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)



2

Właśnie próbowałem to zrobić to MS SQL Server gdzie nie mamy FIELD ():

SELECT table1.id
... 
INNER JOIN
    (VALUES (10,1),(3,2),(4,3),(5,4),(7,5),(8,6),(9,7),(2,8),(6,9),(5,10)
    ) AS X(id,sortorder)
        ON X.id = table1.id
    ORDER BY X.sortorder

Zwróć uwagę, że zezwalam również na kopiowanie.


1

Moją pierwszą myślą było napisanie pojedynczego zapytania, ale powiedziałeś, że nie jest to możliwe, ponieważ jedno jest uruchamiane przez użytkownika, a drugie w tle. Jak przechowujesz listę identyfikatorów, które mają zostać przekazane od użytkownika do procesu w tle? Dlaczego nie umieścić ich w tymczasowej tabeli z kolumną oznaczającą kolejność.

A co powiesz na to:

  1. Bit interfejsu użytkownika uruchamia się i wstawia wartości do nowej utworzonej tabeli. Wstawiłoby identyfikator, stanowisko i jakiś identyfikator numeru pracy)
  2. Numer zadania jest przekazywany do procesu w tle (zamiast wszystkich identyfikatorów)
  3. Proces w tle dokonuje wyboru z tabeli w kroku 1, a Ty dołączasz, aby uzyskać inne wymagane informacje. Używa numeru stanowiska w klauzuli WHERE i zamówień według kolumny stanowisko.
  4. Proces w tle po zakończeniu usuwa z tabeli na podstawie identyfikatora zadania.

1

Myślę, że powinieneś poradzić sobie z przechowywaniem swoich danych w taki sposób, że po prostu wykonasz połączenie i będzie idealne, więc nie ma hacków i skomplikowanych rzeczy.

Mam na przykład listę identyfikatorów utworów „Ostatnio odtwarzane”, na SQLite po prostu robię:

SELECT * FROM recently NATURAL JOIN tracks;

1
Och, chciałbym, żeby życie było takie proste :)
Darryl Hein

0

Spróbuj tego:

SELECT name, description, ...
WHERE id IN
    (SELECT id FROM table1 WHERE...)
ORDER BY
    (SELECT display_order FROM table1 WHERE...),
    (SELECT name FROM table1 WHERE...)

GDZIE prawdopodobnie zajmie trochę czasu, aby skorelowane podzapytania działały poprawnie, ale podstawowa zasada powinna być rozsądna.


zobacz uwagę, a także, jak wspomniano, przykłady zapytań są bardzo uproszczone, więc nie mogę ich połączyć w jedno zapytanie z zapytaniami podrzędnymi.
Darryl Hein
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.