web-dev-qa-db-fra.com

ORDER BY la liste de valeurs IN

J'ai une requête SQL simple dans PostgreSQL 8.3 qui saisit un tas de commentaires. Je fournis une triée liste de valeurs à la construction IN dans la clause WHERE:

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

Cela renvoie les commentaires dans un ordre arbitraire qui, dans mon cas, est un identifiant tel que 1,2,3,4.

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

121
nutcracker

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
79
user80168

Tout simplement parce qu’il est si difficile à trouver et qu’il doit être étendu: dans MySQL, cela peut être fait beaucoup plus simplement , mais je ne sais pas si cela fonctionne dans un autre langage SQL.

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

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
40
vantrung -cuncon

Dans Postgres 9.4 ou une version ultérieure, il s’agit probablement de 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 déjà mentionné .

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

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

Explication détaillée:

36
Erwin Brandstetter

Une autre façon de le faire dans Postgres serait d’utiliser la fonction idx.

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

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

27
Carl Mercier

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

Suppression de la nécessité d'attribuer/de maintenir manuellement une position pour 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 fois. Si ce n'est pas important, cela peut aussi être écrit:

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

Dans Postgresql:

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

En recherchant cela un peu plus, 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 prolixe et pourrait poser des problèmes de performances avec de grands ensembles de données ..___ Quelqu'un peut-il commenter ces problèmes?

4
nutcracker

sans SEQUENCE, ne fonctionne que 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
2
Michael Buen

Pour ce faire, je pense que vous devriez probablement avoir un tableau supplémentaire "ORDER" qui définit le mappage des identifiants à l’ordre (en faisant ce que votre réponse à votre propre question a dit), que vous pouvez ensuite utiliser comme colonne supplémentaire de votre sélection. vous pouvez ensuite trier.

De cette manière, vous décrivez explicitement l'ordre que vous souhaitez dans la base de données, où il devrait être.

2
Paul Sonier
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,')
1
Hafthor

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 voulez classer la liste des tâches par 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

0
Manuel
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 contient un champ appelé ids, et, ids est le tableau dans lequel le comments.id.

passé en postgresql 9.6

0
user6161156
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;

[MODIFIER]

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

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
0
Michael Buen

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 que je sache qui est le gagnant!

Merci a tous :-) 

0
nutcracker

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
Jon Erdman

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

0
Trey