Ordinamento in base all'ordine dei valori in una clausola SQL IN ()


154

Mi chiedo se ci sia un altro modo (forse un modo migliore) per ordinare in base all'ordine dei valori in una clausola IN ().

Il problema è che ho 2 query, una che ottiene tutti gli ID e la seconda che recupera tutte le informazioni. Il primo crea l'ordine degli ID per i quali voglio che il secondo ordini. Gli ID vengono inseriti in una clausola IN () nell'ordine corretto.

Quindi sarebbe qualcosa di simile (estremamente semplificato):

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

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

Il problema è che la seconda query non restituisce i risultati nello stesso ordine in cui gli ID vengono inseriti nella clausola IN ().

Una soluzione che ho trovato è quella di inserire tutti gli ID in una tabella temporanea con un campo a incremento automatico che viene quindi unito alla seconda query.

C'è un'opzione migliore?

Nota: poiché la prima query viene eseguita "dall'utente" e la seconda viene eseguita in un processo in background, non è possibile combinare la query 2 in 1 utilizzando le query secondarie.

Sto usando MySQL, ma sto pensando che potrebbe essere utile che abbia notato quali opzioni ci sono anche per altri DB.

Risposte:


186

Usa la FIELD()funzione di MySQL :

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

FIELD() restituirà l'indice del primo parametro che è uguale al primo parametro (diverso dal primo parametro stesso).

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

restituirà 1

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

tornerà 3

Questo farà esattamente quello che vuoi se incolli gli ID nella IN()clausola e la FIELD()funzione nello stesso ordine.


3
@Vojto l'ordinamento per ordine arbitrario è lento per definizione. Questo fa del suo meglio perché non c'è alcuna garanzia che INe FIELDparametri sono uguali. In questo modo in un codice di programma può essere più veloce utilizzando tale conoscenza aggiuntiva. Naturalmente, potrebbe essere più saggio imporre questo onere sul client anziché sul server se si hanno in mente le prestazioni del server.
ivan_pozdeev,

1
che dire sqlite?
fnc12,

15

Vedi di seguito come ottenere dati ordinati.

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

Due soluzioni che mi vengono in mente:

  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()è di Oracle; forse hai locate()o charindex()o qualcosa del genere)


Per MySQL, potrebbe essere utilizzato anche FIELD_IN_SET (): dev.mysql.com/doc/refman/5.0/en/…
Darryl Hein,

6

Ans per ottenere dati ordinati.

SELECT ...
FROM ...
ORDER  BY FIELD(user_id,5,3,2,...,50)  LIMIT 10

6

Se si desidera eseguire un ordinamento arbitrario su una query utilizzando i valori immessi dalla query in MS SQL Server 2008+ , è possibile creare una tabella al volo e fare un join in questo modo (utilizzando la nomenclatura da 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

Se si sostituisce l'istruzione VALUES con qualcos'altro che fa la stessa cosa, ma in ANSI SQL, questo dovrebbe funzionare su qualsiasi database SQL.

Nota: la seconda colonna nella tabella creata (orderTbl.orderIdx) è necessaria quando si eseguono query su set di record superiori a 100 o più. Inizialmente non avevo una colonna orderIdx, ma ho scoperto che con set di risultati maggiori di 100 dovevo ordinare in modo esplicito per quella colonna; in SQL Server Express 2014 comunque.


Questa domanda riguarda MySQL ... non sono sicuro che la tua query funzioni in MySQL.
Darryl Hein,

2
@ Darryl, non lo sarebbe. Ma questo post si presenta come i migliori risultati quando si google come ordinare dalla clausola IN, quindi ho pensato che potrei anche pubblicarlo qui e la metodologia generale si applica a tutti i server SQL conformi ANSI (basta sostituire l'istruzione VALUES con uno standard modo di creare una tabella).
Ian,

Nota, questo ha funzionato per me, ma solo dopo aver sostituito i riferimenti a orderTbl.orderKey, orderTbl.orderIndexda orderKey,orderIndex
Peter,

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)

ha funzionato davvero alla grande


Ha lavorato per me su Oracle. Facile e veloce. Grazie.
Menachem,

4

La clausola IN descrive un insieme di valori e gli insiemi non hanno ordine.

La tua soluzione con un join e quindi l'ordinamento sulla display_ordercolonna è la soluzione più quasi corretta; qualsiasi altra cosa è probabilmente un hack specifico per DBMS (o sta facendo qualcosa con le funzioni OLAP in SQL standard). Certamente, il join è la soluzione quasi portatile (sebbene la generazione dei dati con i display_ordervalori possa essere problematica). Si noti che potrebbe essere necessario selezionare le colonne di ordinamento; quello era un requisito in SQL standard, anche se credo che sia stato rilassato di regola qualche tempo fa (forse tanto tempo fa come SQL-92).


2

Per Oracle, la soluzione di John che utilizza la funzione instr () funziona. Ecco una soluzione leggermente diversa che ha funzionato: SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)



2

Ho appena provato a fare questo è MS SQL Server in cui non abbiamo 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

Nota che sto permettendo anche la duplicazione.


1

Il mio primo pensiero è stato quello di scrivere una singola query, ma hai detto che non era possibile perché uno è gestito dall'utente e l'altro è eseguito in background. Come stai memorizzando l'elenco di ID da passare dall'utente al processo in background? Perché non inserirli in una tabella temporanea con una colonna per indicare l'ordine.

Che ne dici di questo:

  1. Il bit dell'interfaccia utente viene eseguito e inserisce i valori in una nuova tabella creata. Inserirebbe l'id, la posizione e una sorta di identificatore del numero di lavoro)
  2. Il numero di lavoro viene passato al processo in background (anziché a tutti gli ID)
  3. Il processo in background effettua una selezione dalla tabella nel passaggio 1 e ti unisci per ottenere le altre informazioni richieste. Utilizza il numero di lavoro nella clausola WHERE e ordina per colonna di posizione.
  4. Al termine, il processo in background viene eliminato dalla tabella in base all'identificatore del lavoro.

1

Penso che dovresti riuscire a archiviare i tuoi dati in un modo in cui farai semplicemente un join e sarà perfetto, quindi niente hack e cose complicate in corso.

Ho ad esempio un elenco di ID traccia riprodotti di recente, su SQLite faccio semplicemente:

SELECT * FROM recently NATURAL JOIN tracks;

1
Oh, vorrei che la vita fosse così semplice :)
Darryl Hein,

0

Prova questo:

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

Le WHERE probabilmente impiegheranno un po 'di modifiche per far funzionare correttamente le sottoquery correlate, ma il principio di base dovrebbe essere valido.


vedi nota e anche, come detto, gli esempi di query sono estremamente semplificati, quindi non riesco a combinarli in 1 query con query secondarie.
Darryl Hein,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.