Ordenar por el orden de los valores en una cláusula SQL IN ()


154

Me pregunto si hay una posibilidad (posiblemente una mejor manera) de ordenar por el orden de los valores en una cláusula IN ().

El problema es que tengo 2 consultas, una que obtiene todas las ID y la segunda que recupera toda la información. El primero crea el orden de los ID por los que quiero que el segundo ordene. Los ID se colocan en una cláusula IN () en el orden correcto.

Entonces sería algo como (extremadamente simplificado):

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

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

El problema es que la segunda consulta no devuelve los resultados en el mismo orden en que los ID se colocan en la cláusula IN ().

Una solución que he encontrado es poner todos los ID en una tabla temporal con un campo de incremento automático que luego se une a la segunda consulta.

¿Hay alguna opción mejor?

Nota: Como la primera consulta se ejecuta "por el usuario" y la segunda se ejecuta en un proceso en segundo plano, no hay forma de combinar la consulta 2 en 1 usando subconsultas.

Estoy usando MySQL, pero creo que podría ser útil que tenga en cuenta qué opciones hay para otros DB también.

Respuestas:


186

Use la FIELD()función de MySQL :

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

FIELD() devolverá el índice del primer parámetro que sea igual al primer parámetro (que no sea el primer parámetro en sí).

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

devolverá 1

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

devolverá 3

Esto hará exactamente lo que desea si pega los identificadores en la IN()cláusula y la FIELD()función en el mismo orden.


3
@Vojto ordenar por un orden arbitrario es lento por definición. Esto hace todo lo posible ya que no hay garantía de que INy FIELDparámetros son iguales. Hacer esto en un código de programa puede ser más rápido al usar ese conocimiento adicional. Por supuesto, podría ser más sabio poner esta carga sobre el cliente en lugar del servidor si tiene en cuenta el rendimiento del servidor.
ivan_pozdeev

1
¿qué pasa sqlite?
fnc12

15

Consulte a continuación cómo obtener datos ordenados.

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

Dos soluciones que vienen a la 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()Es de Oracle, puede ser que tenga locate()o charindex()o algo por el estilo)


Para MySQL, FIELD_IN_SET () también podría usarse: dev.mysql.com/doc/refman/5.0/en/…
Darryl Hein

6

Ans para obtener datos ordenados.

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

6

Si desea realizar una ordenación arbitraria en una consulta utilizando los valores ingresados ​​por la consulta en MS SQL Server 2008+ , puede hacerlo creando una tabla sobre la marcha y haciendo una unión como esta (usando la nomenclatura de 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

Si reemplaza la declaración VALUES por otra cosa que haga lo mismo, pero en ANSI SQL, esto debería funcionar en cualquier base de datos SQL.

Nota: La segunda columna en la tabla creada (orderTbl.orderIdx) es necesaria cuando se consultan conjuntos de registros de más de 100. Originalmente no tenía una columna orderIdx, pero descubrí que con conjuntos de resultados superiores a 100 tenía que ordenar explícitamente por esa columna; en SQL Server Express 2014 de todos modos.


Esta pregunta está relacionada con MySQL ... no estoy seguro de que su consulta funcione en MySQL.
Darryl Hein

2
@ Darryl, no lo haría. Pero esta publicación aparece como los mejores resultados cuando buscas en Google cómo ordenar por la cláusula IN, así que pensé que también podría publicarla aquí y la metodología general se aplica a todos los servidores SQL compatibles con ANSI (solo reemplaza la declaración VALUES por un estándar forma de crear una tabla).
Ian

Tenga en cuenta, esto funcionó para mí, pero sólo después de que reemplaza las referencias a orderTbl.orderKey, orderTbl.orderIndexpor orderKey,orderIndex
Pedro

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)

funcionó muy bien


Trabajó para mí en Oracle. Rapido y Facil. Gracias.
Menachem

4

La cláusula IN describe un conjunto de valores, y los conjuntos no tienen orden.

Su solución con unir y luego ordenar en la display_ordercolumna es la solución más correcta; cualquier otra cosa es probablemente un hack específico de DBMS (o está haciendo algunas cosas con las funciones OLAP en SQL estándar). Ciertamente, la unión es la solución más portátil (aunque generar los datos con los display_ordervalores puede ser problemático). Tenga en cuenta que es posible que deba seleccionar las columnas de pedido; eso solía ser un requisito en el SQL estándar, aunque creo que se relajó como regla hace un tiempo (tal vez hace tanto tiempo como SQL-92).


2

Para Oracle, la solución de John que utiliza la función instr () funciona. Aquí hay una solución ligeramente diferente que funcionó: SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)


2

Utilice la función MySQL FIND_IN_SET :

  SELECT * 
    FROM table_name 
   WHERE id IN (..,..,..,..) 
ORDER BY FIND_IN_SET (coloumn_name, .., .., ..);

2

Acabo de intentar hacer esto es MS SQL Server donde no tenemos 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

Tenga en cuenta que también estoy permitiendo la duplicación.


1

Lo primero que pensé fue escribir una sola consulta, pero dijiste que no era posible porque una es ejecutada por el usuario y la otra se ejecuta en segundo plano. ¿Cómo almacena la lista de identificadores para pasar del usuario al proceso en segundo plano? ¿Por qué no ponerlos en una tabla temporal con una columna para indicar el orden?

Entonces, ¿qué tal esto:

  1. El bit de la interfaz de usuario se ejecuta e inserta valores en una nueva tabla que cree. Insertaría la identificación, la posición y algún tipo de identificador de número de trabajo)
  2. El número de trabajo se pasa al proceso en segundo plano (en lugar de todos los identificadores)
  3. El proceso en segundo plano realiza una selección de la tabla en el paso 1 y usted se une para obtener la otra información que necesita. Utiliza el número de trabajo en la cláusula WHERE y ordena por la columna de posición.
  4. El proceso en segundo plano, cuando finaliza, se elimina de la tabla en función del identificador del trabajo.

1

Creo que deberías lograr almacenar tus datos de una manera que simplemente hagas una unión y sea perfecta, para que no ocurran hacks y cosas complicadas.

Tengo, por ejemplo, una lista de "ID reproducidos recientemente" de identificadores de pista, en SQLite simplemente hago:

SELECT * FROM recently NATURAL JOIN tracks;

1
Oh, desearía que la vida fuera así de simple :)
Darryl Hein

0

Dale una oportunidad a esto:

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

Los WHERE probablemente necesitarán algunos ajustes para que las subconsultas correlacionadas funcionen correctamente, pero el principio básico debe ser sólido.


vea la nota y también, como se indicó, los ejemplos de consulta están extremadamente simplificados, por lo que no puedo combinarlos en 1 consulta con subconsultas.
Darryl Hein
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.