Ordenação pela ordem dos valores em uma cláusula SQL IN ()

154

Eu estou querendo saber se existe uma distância (possivelmente uma maneira melhor) para ordenar pela ordem dos valores em uma cláusula IN ().

O problema é que tenho duas consultas, uma que obtém todos os IDs e a segunda que recupera todas as informações. O primeiro cria a ordem dos IDs pelos quais quero que o segundo seja ordenado. Os IDs são colocados em uma cláusula IN () na ordem correta.

Então seria algo como (extremamente simplificado):

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

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

O problema é que a segunda consulta não retorna os resultados na mesma ordem em que os IDs são colocados na cláusula IN ().

Uma solução que encontrei é colocar todos os IDs em uma tabela temporária com um campo de incremento automático, que é então associado à segunda consulta.

Existe uma opção melhor?

Nota: Como a primeira consulta é executada "pelo usuário" e a segunda é executada em um processo em segundo plano, não há como combinar a consulta 2 em 1 usando subconsultas.

Estou usando o MySQL, mas acho que pode ser útil anotar quais opções existem para outros bancos de dados também.

Darryl Hein
fonte

Respostas:

186

Use a FIELD()função do MySQL :

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

FIELD() retornará o índice do primeiro parâmetro igual ao primeiro parâmetro (diferente do próprio parâmetro).

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

retornará 1

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

retornará 3

Isso fará exatamente o que você deseja se colar os IDs na IN()cláusula e na FIELD()função na mesma ordem.

ʞɔıu
fonte
3
A classificação do @Vojto por uma ordem arbitrária é lenta por definição. Isso faz o seu melhor desde que não há nenhuma garantia de que INe FIELDparâmetros são iguais. Fazer isso em um código de programa pode ser mais rápido usando esse conhecimento adicional. Obviamente, pode ser mais sensato colocar essa carga no cliente, e não no servidor, se você tiver em mente o desempenho do servidor.
Ivan_pozdeev
1
que tal sqlite?
Fnc12 23/05
15

Veja a seguir como obter dados classificados.

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
Pradeep Singh
fonte
11

Duas soluções que vêm à 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()É da Oracle, talvez você tem locate()ou charindex()ou algo parecido)

John Nilsson
fonte
Para o MySQL, FIELD_IN_SET () também pode ser usado: dev.mysql.com/doc/refman/5.0/en/…
Darryl Hein
6

Ans para obter dados classificados.

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

Se você deseja fazer uma classificação arbitrária em uma consulta usando valores inseridos pela consulta no MS SQL Server 2008+ , isso pode ser feito criando uma tabela rapidamente e fazendo uma junção assim (usando a nomenclatura do 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

Se você substituir a instrução VALUES por outra coisa que faça a mesma coisa, mas no ANSI SQL, isso funcionará em qualquer banco de dados SQL.

Nota: A segunda coluna na tabela criada (orderTbl.orderIdx) é necessária ao consultar conjuntos de registros maiores que 100 ou mais. Originalmente, não tinha uma coluna orderIdx, mas descobri que, com conjuntos de resultados maiores que 100, era necessário classificar explicitamente por essa coluna; de qualquer maneira no SQL Server Express 2014.

Ian
fonte
Esta pergunta é referente ao MySQL ... não tenho certeza se sua consulta funcionaria no MySQL.
Darryl Hein
2
@ Darryl, não seria. Mas esta postagem aparece como os principais resultados quando você pesquisa no Google como pedir pela cláusula IN, então achei que também poderia publicá-la aqui e a metodologia geral se aplica a todos os servidores SQL compatíveis com ANSI (basta substituir a instrução VALUES por uma norma maneira de criar uma tabela).
Ian
Observe que isso funcionou para mim, mas somente depois que eu substituí as referências a orderTbl.orderKey, orderTbl.orderIndexpor orderKey,orderIndex
Peter
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)

funcionou muito bem

Ravi Ranjan
fonte
Trabalhou para mim no Oracle. Rápido e fácil. Obrigado.
Menachem
4

A cláusula IN descreve um conjunto de valores e os conjuntos não têm ordem.

Sua solução com uma junção e, em seguida, solicitar na display_ordercoluna é a solução mais quase correta; qualquer outra coisa é provavelmente um hack específico do DBMS (ou está fazendo algumas coisas com as funções OLAP no SQL padrão). Certamente, a junção é a solução mais quase portátil (embora a geração de dados com os display_ordervalores possa ser problemática). Observe que pode ser necessário selecionar as colunas de pedidos; isso costumava ser um requisito no SQL padrão, embora eu acredite que ele tenha sido relaxado há algum tempo (talvez até o SQL-92).

Jonathan Leffler
fonte
2

Para Oracle, a solução de John usando a função instr () funciona. Aqui está uma solução ligeiramente diferente que funcionou - SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)

V Patel
fonte
2

Use a função MySQL FIND_IN_SET :

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

Eu apenas tentei fazer isso é o MS SQL Server, onde não temos 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

Note que também estou permitindo duplicação.

Tony
fonte
1

Meu primeiro pensamento foi escrever uma única consulta, mas você disse que isso não era possível porque uma é executada pelo usuário e a outra é executada em segundo plano. Como você está armazenando a lista de IDs a serem transmitidos do usuário para o processo em segundo plano? Por que não colocá-los em uma tabela temporária com uma coluna para indicar o pedido.

Então, e quanto a isso:

  1. O bit da interface do usuário é executado e insere valores em uma nova tabela que você cria. Inseriria a identificação, posição e algum tipo de identificador de número de trabalho)
  2. O número do trabalho é passado para o processo em segundo plano (em vez de todos os IDs)
  3. O processo em segundo plano faz uma seleção na tabela na etapa 1 e você se junta para obter as outras informações necessárias. Ele usa o número do trabalho na cláusula WHERE e ordena pela coluna de posição.
  4. O processo em segundo plano, quando concluído, é excluído da tabela com base no identificador do trabalho.
WW.
fonte
1

Eu acho que você deve conseguir armazenar seus dados de uma maneira que você simplesmente faça uma junção e será perfeito, para que não ocorram hacks e coisas complicadas.

Eu tenho, por exemplo, uma lista de "IDs de faixas" reproduzidas recentemente, no SQLite eu simplesmente faço:

SELECT * FROM recently NATURAL JOIN tracks;
kroe
fonte
1
Ah, eu gostaria que a vida fosse assim tão simples :) #
318 Darryl Hein
0

Faça uma tentativa:

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

Provavelmente, as WHEREs precisarão de alguns ajustes para que as subconsultas correlacionadas funcionem corretamente, mas o princípio básico deve ser sólido.

caos
fonte
veja a nota e também, como declarado, os exemplos de consulta são extremamente simplificados, portanto não posso combiná-los em uma consulta com subconsultas.
Darryl Hein