ORDER BY la liste de valeurs IN


166

J'ai une simple requête SQL dans PostgreSQL 8.3 qui attrape un tas de commentaires. Je fournis une liste triée de valeurs à la INconstruction dans la WHEREclause:

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

Cela renvoie des commentaires dans un ordre arbitraire qui, dans mon cas, est des identifiants similaires 1,2,3,4.

Je veux que les lignes résultantes triées comme la liste dans la INconstruction: (1,3,2,4).
Comment y parvenir?


Et je préfère ne pas créer une nouvelle table juste pour le tri (malgré la pureté SQL).
casse

2
J'ai un tas de réponses maintenant. Puis-je avoir des votes et des commentaires pour savoir qui est le gagnant! Merci à tous :-)
casse

Réponses:


107

Vous pouvez le faire assez facilement avec (introduit dans PostgreSQL 8.2) VALUES (), ().

La syntaxe sera comme ceci:

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 Que faire s'il y a des milliers de valeurs dans la clause IN? parce que je dois le faire pour des milliers de disques
kamal

@kamal Pour cela, j'ai utilisé with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon

66

Simplement parce qu'il est si difficile à trouver et qu'il doit être diffusé: dans mySQL, cela peut être fait beaucoup plus simplement, mais je ne sais pas si cela fonctionne dans d'autres SQL.

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

3
La liste de valeurs doit être fournie deux fois , de deux manières différentes. Pas si simple. La réponse acceptée n'en a besoin qu'une seule fois (même si elle est plus verbeuse). Et c'est encore plus simple avec Postgres moderne (comme démontré dans les nouvelles réponses). En outre, cette question semble concerner Postgres après tout.
Erwin Brandstetter

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

54

Dans Postgres 9.4 ou version ultérieure, c'est probablement le plus simple et le plus rapide :

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • En utilisant le nouveau WITH ORDINALITY, que @a_horse a déjà mentionné .

  • Nous n'avons pas besoin d'une sous-requête, nous pouvons utiliser la fonction de retour de jeu comme une table.

  • Une chaîne littérale à remettre dans le tableau au lieu d'un constructeur ARRAY peut être plus facile à implémenter avec certains clients.

Explication détaillée:


46

Je pense que cette façon est meilleure:

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
J'ai pu le faire avec des valeurs liées, c'est-à-dire: ... order by id=? desc, id=? desc, id=? descet cela semble fonctionner
correctement

Fonctionne en postgres et semble être la meilleure solution!
Mike Szyndel

Cette solution a fait l'affaire pour moi, mais: est-ce que quelqu'un a recherché comment cette solution fonctionne en termes de performances? Il ajoute plusieurs clauses d'ordre par ordre. Par conséquent, il peut (je ne l'ai pas encore testé) devenir plus lent de manière exponentielle avec un nombre croissant d'ID de commande? Toute information à ce sujet serait très appréciée!
Fabian Schöner

1
ERREUR: les listes cibles peuvent avoir au plus 1664 entrées -> lorsque vous essayez d'exécuter une requête longue ...
Fatkhan Fauzi

@Manngo MS SQL. Je ne me souviens plus de quelle version. Cela aurait pu être 2012.
biko

43

Avec Postgres 9.4, cela peut être fait un peu plus court:

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;

Ou un peu plus compact sans table dérivée:

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

Suppression de la nécessité d'attribuer / maintenir manuellement une position à chaque valeur.

Avec Postgres 9.6, cela peut être fait en utilisant 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);

Le CTE est utilisé de sorte que la liste de valeurs ne doit être spécifiée qu'une seule fois. Si ce n'est pas important, cela peut également être écrit comme suit:

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

Cela ne répète pas le tout IN liste de la WHEREclause dans la ORDER BYclause, ce qui en fait la meilleure réponse à mon humble avis ... Maintenant, seulement pour trouver quelque chose de similaire pour MySQL ...
Stijn de Witt

1
Ma réponse préférée, mais notez que array_position ne fonctionne pas avec bigint et que vous devez lancer: order by array_position(array[42,48,43], c.id::int);ce qui peut conduire à des bogues dans certains cas.
aaandre

1
@aaandre Le casting suivant fonctionne bien (dans Postgres 12 au moins) array_position(array[42, 48, 43]::bigint[], c.id::bigint), donc pas besoin de tronquer bigintàint .
Vic

29

Une autre façon de le faire dans Postgres serait d'utiliser la idxfonction.

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

N'oubliez pas de créer d'abord la idxfonction, comme décrit ici: http://wiki.postgresql.org/wiki/Array_Index


11
Cette fonction est désormais disponible dans une extension fournie avec PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Installez-la avec CREATE EXTENSION intarray;.
Alex Kahn

1
Pour les utilisateurs d'Amazon RDS, la fonction de migration ROR enable_extensionvous permettra de l'activer tant que l'utilisateur de votre application est membre du rds_superusergroupe.
Dave S.

dans PG 9.6.2 PG :: UndefinedFunction: ERROR: la fonction idx (integer [], integer) n'existe pas
Yakob Ubaidi

Merci, meilleure réponse lorsqu'elle est combinée avec le commentaire de @ AlexKahn
Andrew

21

Dans Postgresql:

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

2
Hum ... ça dérange si position(id::text in '123,345,3,678'). L'identifiant 3correspondra avant l'identifiant 345, n'est-ce pas?
alanjds

4
Je pense que vous avez raison et que vous auriez besoin d'un délimiteur de début et de fin, peut-être comme: order by position (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush

3

En recherchant un peu plus cela, j'ai trouvé cette solution:

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

Cependant, cela semble plutôt verbeux et peut avoir des problèmes de performances avec de grands ensembles de données. Quelqu'un peut-il commenter ces questions?


7
Bien sûr, je peux les commenter. Il y a des choses pour lesquelles SQL est bon et des choses pour lesquelles il n'est pas bon. SQL n'est pas bon pour cela. Triez simplement les résultats dans la langue à partir de laquelle vous effectuez les requêtes; cela vous évitera beaucoup de gémissements et de grincements de dents. SQL est un langage orienté ensemble et les ensembles ne sont pas des collections ordonnées.
kquinn

Hmmm ... Est-ce basé sur une expérience personnelle et des tests? Mon expérience testée est que c'est une technique assez efficace pour commander. (Cependant, la réponse acceptée est globalement meilleure car elle élimine la clause "IN (...)"). N'oubliez pas que pour toute taille d'ensemble de résultats raisonnable, la dérivation de l'ensemble devrait être la partie la plus coûteuse. Une fois qu'il est réduit à plusieurs centaines d'enregistrements ou moins, le tri est trivial.
dkretz

Et s'il y a des milliers de valeurs dans la INclause? parce que je dois le faire pour des milliers de disques.
kamal

2

Pour ce faire, je pense que vous devriez probablement avoir une table "ORDER" supplémentaire qui définit le mappage des identifiants à commander (faisant effectivement ce que votre réponse à votre propre question a dit), que vous pouvez ensuite utiliser comme colonne supplémentaire sur votre sélection vous pouvez ensuite trier.

De cette façon, vous décrivez explicitement l'ordre que vous désirez dans la base de données, là où il devrait être.


Cela semble être la bonne façon de procéder. Cependant, j'aimerais créer cette table de commande à la volée. J'ai suggéré d'utiliser un tableau constant dans l'une des réponses. Cela va-t-il être performant lorsque je traite des centaines ou des milliers de commentaires?
casse

2

sans SEQUENCE, fonctionne uniquement sur 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

ou si vous préférez le mal au bien:

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

0

Et voici une autre solution qui fonctionne et utilise une table constante ( 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

Mais encore une fois, je ne suis pas sûr que ce soit performant.

J'ai un tas de réponses maintenant. Puis-je avoir des votes et des commentaires pour savoir qui est le gagnant!

Merci a tous :-)


1
votre réponse est presque la même avec depesz, supprimez simplement le c.ID IN (1,3,2,4). de toute façon, c'est mieux, il utilise JOIN, autant que possible, utilise la méthode ANSI SQL pour joindre, n'utilise pas de table de virgule. J'aurais dû lire attentivement votre réponse, j'ai du mal à comprendre comment aliaser les deux colonnes, j'ai d'abord essayé ceci: (values ​​(1,1) as x (id, sort_order), (3,2), (2,3), (4,4)) comme y. mais en vain :-D votre réponse aurait pu me fournir un indice si je l'ai lu attentivement :-)
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;

[ÉDITER]

unnest n'est pas encore intégré à 8.3, mais vous pouvez en créer un vous-même (la beauté de n'importe quel *):

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;
$$;

cette fonction peut fonctionner dans n'importe quel type:

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

Merci Michael mais la fonction unnest ne semble pas exister pour mon PSQL et je ne trouve aucune mention à ce sujet dans la documentation non plus. Est-ce seulement 8.4?
casse

unnest n'est pas encore intégré à 8.3, mais vous pouvez en implémenter un vous-même. voir le code ci
Michael Buen

0

Légère amélioration par rapport à la version qui utilise une séquence je pense:

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)

ici, [bbs] est la table principale qui a un champ appelé ids, et, ids est le tableau qui stocke le comments.id.

passé dans postgresql 9.6


avez-vous testé cette requête?
lalithkumar

ici, rappelez-vous, ids est un type de tableau, comme {1,2,3,4}.
user6161156

0

Permet d'avoir une impression visuelle de ce qui a déjà été dit. Par exemple, vous avez une table avec certaines tâches:

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

Et vous souhaitez classer la liste des tâches en fonction de son statut. Le statut est une liste de valeurs de chaîne:

(processing, pending,  completed, deleted)

L'astuce consiste à donner à chaque valeur de statut un entier et à ordonner la liste numérique:

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;

Qui conduit à:

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

Crédit @ user80168


-1

Je suis d'accord avec toutes les autres affiches qui disent "ne faites pas ça" ou "SQL n'est pas bon pour ça". Si vous souhaitez trier selon une facette de commentaires, ajoutez une autre colonne entière à l'une de vos tables pour contenir vos critères de tri et trier par cette valeur. par exemple "ORDER BY comments.sort DESC" Si vous voulez les trier dans un ordre différent à chaque fois, alors ... SQL ne sera pas pour vous dans ce cas.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.