ORDENAR POR la ​​lista de valores IN

166

Tengo una consulta SQL simple en PostgreSQL 8.3 que toma muchos comentarios. Proporciono una lista ordenada de valores a la INconstrucción en la WHEREcláusula:

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

Esto devuelve comentarios en un orden arbitrario que, en mi caso, son identificadores 1,2,3,4.

Quiero que las filas resultantes ordenados como la lista de la INconstrucción: (1,3,2,4).
¿Cómo lograr eso?

cascanueces
fuente
Y preferiría no crear una nueva tabla solo para la clasificación (a pesar de la pureza de SQL).
cascanueces
2
Tengo muchas respuestas ahora. ¿Puedo obtener algunas votaciones y comentarios para saber cuál es el ganador! Gracias a todos :-)
cascanueces

Respuestas:

107

Puede hacerlo con bastante facilidad con (introducido en PostgreSQL 8.2) VALUES (), ().

La sintaxis será así:

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

fuente
2
@ user80168 ¿Qué sucede si hay miles de valores en la cláusula IN? porque tengo que hacerlo por miles de registros
kamal
@kamal Para eso he usado with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon
66

Solo porque es muy difícil de encontrar y tiene que extenderse: en mySQL esto se puede hacer mucho más simple , pero no sé si funciona en otro SQL.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
das oe
fuente
3
La lista de valores debe proporcionarse dos veces , de dos maneras diferentes. No es tan simple. La respuesta aceptada solo la necesita una vez (aunque sea de una manera más detallada). Y es aún más simple con Postgres modernos (como se demuestra en las respuestas más recientes). Además, esta pregunta parece ser sobre Postgres después de todo.
Erwin Brandstetter
8
ERROR: cannot pass more than 100 arguments to a function
brauliobo
54

En Postgres 9.4 o posterior, esto es probablemente el más simple y rápido :

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • Usando el nuevo WITH ORDINALITY, que @a_horse ya mencionó .

  • No necesitamos una subconsulta, podemos usar la función set-return como una tabla.

  • Una cadena literal para entregar en la matriz en lugar de un constructor ARRAY puede ser más fácil de implementar con algunos clientes.

Explicación detallada:

Erwin Brandstetter
fuente
46

Creo que de esta manera es mejor:

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
vantrung -cuncon
fuente
1
Pude hacer esto con valores vinculados, es decir: ... order by id=? desc, id=? desc, id=? descy parece funcionar bien :-)
KajMagnus
¡Funciona en postgres y parece ser la mejor solución!
Mike Szyndel
Esta solución me sirvió, pero: ¿Alguien investigó cómo funciona esta solución en términos de rendimiento? Agrega orden múltiple por cláusulas. Por lo tanto, ¿puede (aún no lo probé) disminuir más exponencialmente con un número creciente de ID de pedido? ¡Cualquier información sobre esto sería muy apreciada!
Fabian Schöner
1
ERROR: las listas de destino pueden tener como máximo 1664 entradas -> cuando intenta ejecutar una consulta larga ...
Fatkhan Fauzi
@Manngo MS SQL. No recuerdo qué versión. Podría haber sido 2012.
biko
43

Con Postgres 9.4 esto se puede hacer un poco más corto:

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 poco más compacto sin una tabla derivada:

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

Eliminando la necesidad de asignar / mantener manualmente una posición para cada valor.

Con Postgres 9.6 esto se puede hacer 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);

El CTE se usa para que la lista de valores solo necesite especificarse una vez. Si eso no es importante, esto también se puede escribir como:

select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);
un caballo sin nombre
fuente
Esto no repite toda la INlista de la WHEREcláusula nuevamente en la ORDER BYcláusula, lo que hace que esta sea la mejor respuesta en mi humilde opinión ... Ahora solo para encontrar algo similar para MySQL ...
Stijn de Witt
1
Mi respuesta favorita, pero tenga en cuenta que array_position no funciona con bigint y que tendría que emitir: lo order by array_position(array[42,48,43], c.id::int);que puede provocar errores en algunos casos.
aaandre
1
@aaandre La siguiente colada está funcionando bien (en Postgres al menos 12) array_position(array[42, 48, 43]::bigint[], c.id::bigint), por lo que no hay necesidad de truncar biginta int.
Vic
29

Otra forma de hacerlo en Postgres sería usar la idxfunción.

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

No olvide crear idxprimero la función, como se describe aquí: http://wiki.postgresql.org/wiki/Array_Index

Carl Mercier
fuente
11
Esta función ahora está disponible en una extensión que viene con PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Instalar con CREATE EXTENSION intarray;.
Alex Kahn
1
Simplemente acumulando más, para los usuarios de Amazon RDS, la función de migración ROR enable_extensionle permitirá activar esto siempre que el usuario de su aplicación sea miembro del rds_superusergrupo.
Dave S.
en PG 9.6.2 PG :: Función indefinida: ERROR: la función idx (entero [], entero) no existe
Yakob Ubaidi
Gracias, la mejor respuesta cuando se combina con el comentario de @ AlexKahn
Andrew
21

En Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
Clodoaldo Neto
fuente
2
Hum ... bugs si position(id::text in '123,345,3,678'). La identificación 3coincidirá antes que la identificación 345, ¿no?
alanjds
44
Creo que tiene razón y necesitaría tener un delimitador inicial y final, tal vez como: ordenar por posición (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush
3

Al investigar esto un poco más, encontré esta solución:

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

Sin embargo, esto parece bastante detallado y podría tener problemas de rendimiento con grandes conjuntos de datos. ¿Alguien puede comentar sobre estos temas?

cascanueces
fuente
77
Claro, puedo comentar sobre ellos. Hay cosas en las que SQL es bueno, y cosas en las que no es bueno. SQL no es bueno en esto. Simplemente ordene los resultados en el idioma desde el que realiza las consultas; te ahorrará mucho lamento y crujir de dientes. SQL es un lenguaje orientado a conjuntos, y los conjuntos no son colecciones ordenadas.
kquinn
Hmmm ... ¿Se basa eso en experiencia personal y pruebas? Mi experiencia probada es que esta es una técnica bastante efectiva para ordenar. (Sin embargo, la respuesta aceptada es mejor en general porque elimina la cláusula "IN (...)"). Recuerde que para cualquier tamaño de conjunto de resultados razonable, derivar el conjunto debería ser la parte costosa. Una vez que se reduce a varios cientos de registros o menos, la clasificación es trivial.
dkretz 05 de
¿Qué pasa si hay miles de valores en la INcláusula? porque tengo que hacerlo por miles de registros.
kamal
2

Para hacer esto, creo que probablemente debería tener una tabla "ORDEN" adicional que defina el mapeo de ID para ordenar (efectivamente haciendo lo que dice su respuesta a su propia pregunta), que luego puede usar como una columna adicional en su selección entonces puedes ordenar.

De esa manera, usted describe explícitamente el orden que desea en la base de datos, donde debería estar.

Paul Sonier
fuente
Esta parece ser la forma correcta de hacerlo. Sin embargo, me gustaría crear esa tabla de pedidos sobre la marcha. Sugerí usar una tabla constante en una de las respuestas. ¿Será efectivo cuando me ocupe de cientos o miles de comentarios?
cascanueces
2

sans SEQUENCE, funciona solo en 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
Michael Buen
fuente
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 si prefieres el mal al bien:

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

Y aquí hay otra solución que funciona y utiliza una tabla 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

Pero, una vez más, no estoy seguro de que esto sea efectivo.

Tengo muchas respuestas ahora. ¿Puedo obtener algunas votaciones y comentarios para saber cuál es el ganador!

Gracias a todos :-)

cascanueces
fuente
1
su respuesta es casi la misma con depesz, simplemente quite el c.ID IN (1,3,2,4). de todos modos, el suyo es mejor, usa JOIN, tanto como sea posible use la forma de unión ANSI SQL, no use la tabla tabla de comas. Debería haber leído su respuesta con cuidado, me está costando mucho descubrir cómo alias las dos columnas, primero probé esto: (valores (1,1) como x (id, sort_order), (3,2), (2,3), (4,4)) como y. pero fue en vano :-D su respuesta podría haberme dado una pista si lo he leído cuidadosamente :-)
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;

[EDITAR]

unnest aún no está incorporado en 8.3, pero puede crear uno usted mismo (la belleza de cualquier *):

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

esa función puede funcionar en cualquier tipo:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Michael Buen
fuente
Gracias Michael, pero la función desagradable no parece existir para mi PSQL y tampoco puedo encontrar ninguna mención de ella en los documentos. ¿Es solo 8.4?
cascanueces
Unnest aún no está incorporado en 8.3, pero puede implementar uno usted mismo. ver el código de arriba
Michael Buen
0

Ligera mejora sobre la versión que usa una secuencia, creo:

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;

fuente
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)

aquí, [bbs] es la tabla principal que tiene un campo llamado ids, y, ids es la matriz que almacena los comentarios.id.

aprobado en postgresql 9.6

usuario6161156
fuente
probaste esta consulta?
lalithkumar
Aquí, recuerde, ID es un tipo de matriz, como, {1,2,3,4}.
user6161156
0

Vamos a tener una impresión visual de lo que ya se dijo. Por ejemplo, tiene una tabla con algunas tareas:

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

Y desea ordenar la lista de tareas por su estado. El estado es una lista de valores de cadena:

(processing, pending,  completed, deleted)

El truco es dar a cada valor de estado un número entero y ordenar la lista numérica:

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;

Lo que lleva a:

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

Crédito @ user80168

Manuel
fuente
-1

Estoy de acuerdo con todos los otros carteles que dicen "no hagas eso" o "SQL no es bueno para eso". Si desea ordenar por alguna faceta de comentarios, agregue otra columna entera a una de sus tablas para mantener sus criterios de clasificación y ordenar por ese valor. por ejemplo, "ORDER BY comments.sort DESC" Si desea ordenarlos en un orden diferente cada vez que ... SQL no será para usted en este caso.

Trey
fuente