ORDINA DALL'elenco dei valori IN


166

Ho una semplice query SQL in PostgreSQL 8.3 che raccoglie molti commenti. Fornisco un elenco ordinato di valori per il INcostrutto nella WHEREclausola:

SELECT * FROM comments WHERE (comments.id IN (1,3,2,4));

Questo restituisce commenti in un ordine arbitrario che nel mio caso sembra essere un ID 1,2,3,4.

Voglio che le righe risultanti ordinati come la lista nel INcostrutto: (1,3,2,4).
Come raggiungerlo?


E preferirei non creare una nuova tabella solo per l'ordinamento (nonostante la purezza di SQL).
schiaccianoci,

2
Ho un sacco di risposte ora. Posso ottenere alcuni voti e commenti in modo da sapere qual è il vincitore! Grazie a tutti :-)
schiaccianoci,

Risposte:


107

Puoi farlo abbastanza facilmente con (introdotto in PostgreSQL 8.2) VALUES (), ().

La sintassi sarà così:

select c.*
from comments c
join (
  values
    (1,1),
    (3,2),
    (2,3),
    (4,4)
) as x (id, ordering) on c.id = x.id
order by x.ordering

2
@ user80168 Cosa succede se ci sono migliaia di valori nella clausola IN? perché devo farlo per migliaia di dischi
Kamal,

@kamal Per quello che ho usato with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon,

66

Solo perché è così difficile da trovare e deve essere diffuso: in mySQL questo può essere fatto molto più semplice , ma non so se funziona in altri SQL.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')

3
L'elenco dei valori deve essere fornito due volte , in due modi diversi. Non così semplice La risposta accettata ne ha bisogno solo una volta (anche se in modo più dettagliato). Ed è ancora più semplice con i moderni Postgres (come dimostrato nelle risposte più recenti). Inoltre, questa domanda sembra riguardare Postgres dopo tutto.
Erwin Brandstetter,

8
ERROR: cannot pass more than 100 arguments to a function
brauliobo,

54

In Postgres 9.4 o successivo, questo è probabilmente il più semplice e veloce :

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • Usando il nuovo WITH ORDINALITY, che @a_horse ha già menzionato .

  • Non abbiamo bisogno di una sottoquery, possiamo usare la funzione di restituzione del set come una tabella.

  • Una stringa letterale da consegnare nell'array anziché in un costruttore ARRAY può essere più facile da implementare con alcuni client.

Spiegazione dettagliata:


46

Penso che in questo modo sia meglio:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
    ORDER BY  id=1 DESC, id=3 DESC, id=2 DESC, id=4 DESC

1
Sono stato in grado di farlo con valori associati, vale a dire: ... order by id=? desc, id=? desc, id=? desce sembra funzionare bene :-)
KajMagnus

Funziona in Postgres e sembra essere la soluzione migliore!
Mike Szyndel,

Questa soluzione ha funzionato per me, ma: qualcuno ha fatto ricerche su come questa soluzione stia facendo prestazioni? Aggiunge un ordine multiplo per clausole. Quindi potrebbe (non l'ho ancora testato) rallentare esponenzialmente con l'aumentare del numero di ID ordine? Qualsiasi informazione al riguardo sarebbe molto apprezzata!
Fabian Schöner,

1
ERRORE: gli elenchi di destinazione possono contenere al massimo 1664 voci -> quando si tenta di eseguire query lunghe ...
Fatkhan Fauzi

@Manngo MS SQL. Non ricordo quale versione. Potrebbe essere stato il 2012.
biko

43

Con Postgres 9.4 questo può essere fatto un po 'più breve:

select c.*
from comments c
join (
  select *
  from unnest(array[43,47,42]) with ordinality
) as x (id, ordering) on c.id = x.id
order by x.ordering;

O un po 'più compatto senza una tabella derivata:

select c.*
from comments c
  join unnest(array[43,47,42]) with ordinality as x (id, ordering) 
    on c.id = x.id
order by x.ordering

Rimozione della necessità di assegnare / mantenere manualmente una posizione per ciascun valore.

Con Postgres 9.6 questo può essere fatto usando array_position():

with x (id_list) as (
  values (array[42,48,43])
)
select c.*
from comments c, x
where id = any (x.id_list)
order by array_position(x.id_list, c.id);

Il CTE viene utilizzato in modo tale che l'elenco di valori debba essere specificato una sola volta. Se ciò non è importante, questo può anche essere scritto come:

select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);

Questo non ripete l'intero INelenco dalla WHEREclausola di nuovo nella ORDER BYclausola, il che rende questa la migliore risposta imho ... Ora solo per trovare qualcosa di simile per MySQL ...
Stijn de Witt,

1
La mia risposta preferita ma nota che array_position non funziona con bigint e avresti bisogno di lanciare: il order by array_position(array[42,48,43], c.id::int);che, in alcuni casi, potrebbe portare a bug.
aaandre,

1
@aaandre Il seguente colata sta funzionando benissimo (in Postgres 12 almeno) array_position(array[42, 48, 43]::bigint[], c.id::bigint), quindi non è necessario per troncare biginta int.
Vic

29

Un altro modo per farlo in Postgres sarebbe usare la idxfunzione.

SELECT *
FROM comments
ORDER BY idx(array[1,3,2,4], comments.id)

Non dimenticare di creare idxprima la funzione, come descritto qui: http://wiki.postgresql.org/wiki/Array_Index


11
Questa funzione è ora disponibile in un'estensione fornita con PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Installalo con CREATE EXTENSION intarray;.
Alex Kahn,

1
Accumulando ulteriormente, per gli utenti Amazon RDS, la funzione di migrazione ROR enable_extensionti consentirà di attivarlo fintanto che l'utente della tua app è membro del rds_superusergruppo.
Dave S.

in PG 9.6.2 PG :: UndefinedFunzione: ERRORE: la funzione idx (numero intero [], numero intero) non esiste
Yakob Ubaidi

Grazie, migliore risposta in combinazione con il commento di @ AlexKahn
Andrew

21

In Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')

2
Hum ... bug se position(id::text in '123,345,3,678'). L'ID 3corrisponderà prima dell'id 345, vero?
Alanjds,

4
Penso che tu abbia ragione e che dovresti avere sia un delimitatore di inizio che di fine, quindi forse: ordina per posizione (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush,

3

Sulla ricerca di questo ancora ho trovato questa soluzione:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4)) 
ORDER BY CASE "comments"."id"
WHEN 1 THEN 1
WHEN 3 THEN 2
WHEN 2 THEN 3
WHEN 4 THEN 4
END

Tuttavia, questo sembra piuttosto dettagliato e potrebbe avere problemi di prestazioni con set di dati di grandi dimensioni. Qualcuno può commentare questi problemi?


7
Certo, posso commentarli. Ci sono cose in cui SQL è bravo e cose in cui non è bravo. SQL non è bravo in questo. Basta ordinare i risultati in qualunque lingua tu stia facendo le domande; ti farà risparmiare molto lamenti e digrignare i denti. SQL è un linguaggio orientato ai set e i set non sono raccolte ordinate.
kquinn,

Hmmm ... È basato sull'esperienza personale e sui test? La mia esperienza testata è che questa è una tecnica abbastanza efficace per ordinare. (Tuttavia, la risposta accettata è nel complesso migliore perché elimina la clausola "IN (...)"). Ricorda che per qualsiasi ragionevole dimensione del set di risultati, derivare il set dovrebbe essere la parte costosa. Una volta che è sceso a diverse centinaia di record o meno, l'ordinamento è banale.
dkretz,

Cosa succede se ci sono migliaia di valori nella INclausola? perché devo farlo per migliaia di dischi.
Kamal,

2

Per fare questo, penso che dovresti probabilmente avere una tabella "ORDER" aggiuntiva che definisce la mappatura degli ID da ordinare (facendo effettivamente ciò che ha detto la tua risposta alla tua domanda), che puoi quindi usare come colonna aggiuntiva sulla tua selezione che è quindi possibile ordinare.

In questo modo, descrivi esplicitamente l'ordine che desideri nel database, dove dovrebbe essere.


Questo sembra il modo giusto per farlo. Tuttavia, vorrei creare al volo quella tabella degli ordini. Ho suggerito di utilizzare una tabella costante in una delle risposte. Sarà performante quando avrò a che fare con centinaia o migliaia di commenti?
schiaccianoci,

2

sans SEQUENCE, funziona solo su 8.4:

select * from comments c
join 
(
    select id, row_number() over() as id_sorter  
    from (select unnest(ARRAY[1,3,2,4]) as id) as y
) x on x.id = c.id
order by x.id_sorter

1
SELECT * FROM "comments" JOIN (
  SELECT 1 as "id",1 as "order" UNION ALL 
  SELECT 3,2 UNION ALL SELECT 2,3 UNION ALL SELECT 4,4
) j ON "comments"."id" = j."id" ORDER BY j.ORDER

o se preferisci il male al bene:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')

0

Ed ecco un'altra soluzione che funziona e utilizza una tabella costante ( http://www.postgresql.org/docs/8.3/interactive/sql-values.html ):

SELECT * FROM comments AS c,
(VALUES (1,1),(3,2),(2,3),(4,4) ) AS t (ord_id,ord)
WHERE (c.id IN (1,3,2,4)) AND (c.id = t.ord_id)
ORDER BY ord

Ma di nuovo non sono sicuro che questo sia performante.

Ho un sacco di risposte ora. Posso ottenere alcuni voti e commenti in modo da sapere qual è il vincitore!

Ringrazia tutti :-)


1
la tua risposta è quasi la stessa con depesz, basta rimuovere il c.ID IN (1,3,2,4). comunque è meglio, usa JOIN, per quanto possibile usa il modo ANSI SQL di unire, non usare la tabella delle virgole della tabella. avrei dovuto leggere attentamente la tua risposta, sto avendo difficoltà a capire come alias le due colonne, prima ho provato questo: (valori (1,1) come x (id, sort_order), (3,2), (2,3), (4,4)) come y. ma inutilmente MrGreen la tua risposta avrebbe potuto darmi un indizio se l'avessi letta attentamente :-)
Michael Buen,

0
create sequence serial start 1;

select * from comments c
join (select unnest(ARRAY[1,3,2,4]) as id, nextval('serial') as id_sorter) x
on x.id = c.id
order by x.id_sorter;

drop sequence serial;

[MODIFICARE]

unnest non è ancora integrato in 8.3, ma puoi crearne uno tu stesso (la bellezza di qualsiasi *):

create function unnest(anyarray) returns setof anyelement
language sql as
$$
    select $1[i] from generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;

quella funzione può funzionare in qualsiasi tipo:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id

Grazie Michael, ma la funzione più inutile non sembra esistere per il mio PSQL e non riesco a trovare alcuna menzione nei documenti. È solo 8.4?
schiaccianoci,

unnest non è ancora integrato in 8.3, ma puoi implementarlo tu stesso. vedi il codice sopra
Michael Buen,

0

Leggero miglioramento rispetto alla versione che utilizza una sequenza penso:

CREATE OR REPLACE FUNCTION in_sort(anyarray, out id anyelement, out ordinal int)
LANGUAGE SQL AS
$$
    SELECT $1[i], i FROM generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;

SELECT 
    * 
FROM 
    comments c
    INNER JOIN (SELECT * FROM in_sort(ARRAY[1,3,2,4])) AS in_sort
        USING (id)
ORDER BY in_sort.ordinal;

0
select * from comments where comments.id in 
(select unnest(ids) from bbs where id=19795) 
order by array_position((select ids from bbs where id=19795),comments.id)

qui, [bbs] è la tabella principale che ha un campo chiamato ids e ids è l'array che memorizza il comment.id.

passato in postgresql 9.6


hai testato questa query?
lalithkumar

qui, ricorda, ids è un tipo di array, come {1,2,3,4}.
user6161156

0

Diamo un'impressione visiva di ciò che è già stato detto. Ad esempio, hai una tabella con alcune attività:

SELECT a.id,a.status,a.description FROM minicloud_tasks as a ORDER BY random();

 id |   status   |   description    
----+------------+------------------
  4 | processing | work on postgres
  6 | deleted    | need some rest
  3 | pending    | garden party
  5 | completed  | work on html

E vuoi ordinare l'elenco delle attività in base al suo stato. Lo stato è un elenco di valori stringa:

(processing, pending,  completed, deleted)

Il trucco è dare a ogni valore di stato un numero intero e ordinare un elenco numerico:

SELECT a.id,a.status,a.description FROM minicloud_tasks AS a
  JOIN (
    VALUES ('processing', 1), ('pending', 2), ('completed', 3), ('deleted', 4)
  ) AS b (status, id) ON (a.status = b.status)
  ORDER BY b.id ASC;

Il quale conduce a:

 id |   status   |   description    
----+------------+------------------
  4 | processing | work on postgres
  3 | pending    | garden party
  5 | completed  | work on html
  6 | deleted    | need some rest

Credito @ user80168


-1

Sono d'accordo con tutti gli altri poster che dicono "non farlo" o "SQL non è bravo in questo". Se si desidera ordinare in base a un certo aspetto dei commenti, aggiungere un'altra colonna intera a una delle tabelle per contenere i criteri di ordinamento e ordinare in base a quel valore. ad es. "ORDINA PER commenti.sort DESC" Se si desidera ordinare questi in un ordine diverso ogni volta, in questo caso SQL non fa per voi.

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.