ORDER BY a lista de valores IN


166

Eu tenho uma consulta SQL simples no PostgreSQL 8.3 que pega um monte de comentários. Eu forneço uma lista classificada de valores para a INconstrução na WHEREcláusula:

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

Isso retorna comentários em uma ordem arbitrária que, no meu caso, é como ids 1,2,3,4.

Quero que as linhas resultantes classificadas como na lista da INconstrução: (1,3,2,4).
Como conseguir isso?


E eu preferiria não criar uma nova tabela apenas para a classificação (apesar da pureza do SQL).
quebra-nozes

2
Eu tenho um monte de respostas agora. Posso obter algumas votações e comentários para saber qual é o vencedor! Obrigado a todos :-)
quebra-nozes de

Respostas:


107

Você pode fazer isso facilmente com (introduzido no PostgreSQL 8.2) VALUES (), ().

A sintaxe será assim:

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 E se houver milhares de valores na cláusula IN? porque eu tenho que fazer isso por milhares de registros
kamal

@kamal Por isso eu usei with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon

66

Só porque é tão difícil de encontrar e precisa ser espalhado: no mySQL isso pode ser feito de maneira muito mais simples , mas não sei se funciona em outro SQL.

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

3
A lista de valores deve ser fornecida duas vezes , de duas maneiras diferentes. Não tão simples. A resposta aceita precisa apenas uma vez (mesmo que de maneira mais detalhada). E é ainda mais simples com o Postgres moderno (como demonstrado nas respostas mais recentes). Além disso, essa questão parece ser sobre o Postgres, afinal.
Erwin Brandstetter

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

54

No Postgres 9.4 ou posterior, isso é provavelmente mais simples e 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 o novo WITH ORDINALITY, que @a_horse já mencionou .

  • Não precisamos de uma subconsulta, podemos usar a função de retorno de conjunto como uma tabela.

  • Uma string literal para entregar na matriz em vez de um construtor ARRAY pode ser mais fácil de implementar com alguns clientes.

Explicação detalhada:


46

Eu acho que assim é melhor:

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
Eu era capaz de fazer isso com valores vinculados, ou seja: ... order by id=? desc, id=? desc, id=? desce parece funcionar bem :-)
KajMagnus

Funciona no postgres e parece ser a melhor solução!
Mike Szyndel

Essa solução fez o truque para mim, mas: Alguém pesquisou o desempenho dessa solução? Ele adiciona ordem múltipla por cláusulas. Portanto, pode (eu ainda não testei) ficar mais lento exponencialmente com o aumento do número de IDs de pedidos? Qualquer informação sobre isso seria muito apreciada!
Fabian Schöner

1
ERRO: as listas de alvos podem ter no máximo 1664 entradas -> quando você tenta executar uma consulta longa ...
Fatkhan Fauzi 13/16

@Manngo MS SQL. Não consigo lembrar qual versão. Pode ter sido em 2012.
biko

43

Com o Postgres 9.4, isso pode ser feito um pouco mais curto:

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 um pouco mais compacto sem uma tabela 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

Removendo a necessidade de atribuir / manter manualmente uma posição para cada valor.

Com o Postgres 9.6, isso pode ser feito 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);

O CTE é usado para que a lista de valores precise ser especificada apenas uma vez. Se isso não for importante, também pode ser escrito como:

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

Isso não repetir o todo INlista da WHEREcláusula novamente na ORDER BYcláusula, o que torna esta a melhor resposta imho ... Agora só para encontrar algo semelhante para MySQL ...
Stijn de Witt

1
Minha resposta favorita, mas observe que array_position não funciona com bigint e você precisaria converter: o order by array_position(array[42,48,43], c.id::int);que pode levar a erros em alguns casos.
aaandre 27/09/19

1
@aaandre A seguir fundição está funcionando bem (em Postgres 12 pelo menos) array_position(array[42, 48, 43]::bigint[], c.id::bigint), então não há necessidade de truncar biginta int.
Vic

29

Outra maneira de fazer isso no Postgres seria usar a idxfunção

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

Não se esqueça de criar a idxfunção primeiro, conforme descrito aqui: http://wiki.postgresql.org/wiki/Array_Index


11
Esta função está agora disponível em uma extensão que vem com o PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Instale-o com CREATE EXTENSION intarray;.
Alex Kahn

1
Para aumentar ainda mais, para os usuários do Amazon RDS, a função de migração do ROR enable_extensionpermitirá que você ative isso desde que o usuário do aplicativo seja um membro do rds_superusergrupo.
Dave S.

em PG PG 9.6.2 :: UndefinedFunction: ERROR: idx função (inteiro [], um número inteiro) não existe
Yakob Ubaidi

Obrigado, melhor resposta quando combinado com @ comentário de AlexKahn
Andrew

21

No Postgresql:

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

2
Hum ... isso incomoda position(id::text in '123,345,3,678'). O ID 3corresponderá antes do ID 345, não é?
Alanjds

4
Eu acho que você está certo e precisaria ter um delimitador inicial e final, talvez como: classificar por posição (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush

3

Ao pesquisar isso um pouco mais, encontrei esta solução:

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

No entanto, isso parece bastante detalhado e pode ter problemas de desempenho com grandes conjuntos de dados. Alguém pode comentar sobre essas questões?


7
Claro, eu posso comentar sobre eles. Há coisas em que o SQL é bom e coisas em que não é bom. SQL não é bom nisso. Basta classificar os resultados no idioma em que você estiver fazendo as consultas; isto lhe poupará muitos gemidos e ranger de dentes. SQL é uma linguagem orientada a conjuntos, e conjuntos não são coleções ordenadas.
22410 kquinn

Hmmm ... Isso é baseado em experiências e testes pessoais? Minha experiência testada é que essa é uma técnica bastante eficaz para fazer pedidos. (No entanto, a resposta aceita é melhor em geral porque elimina a cláusula "IN (...)"). Lembre-se de que, para qualquer tamanho razoável de conjunto de resultados, derivar o conjunto deve ser a parte mais cara. Depois de diminuir para várias centenas de registros ou menos, a classificação é trivial.
dkretz

E se houver milhares de valores na INcláusula? porque eu tenho que fazer isso por milhares de registros.
kamal

2

Para fazer isso, acho que você provavelmente deveria ter uma tabela "ORDER" adicional que define o mapeamento dos IDs por ordem (efetivamente fazendo o que sua resposta à sua própria pergunta dizia), que você pode usar como uma coluna adicional no seu você pode então escolher.

Dessa forma, você descreve explicitamente a ordem que deseja no banco de dados, onde deveria estar.


Este parece ser o caminho certo para fazê-lo. No entanto, eu gostaria de criar essa tabela de pedidos rapidamente. Sugeri o uso de uma tabela constante em uma das respostas. Isso terá desempenho quando eu estiver lidando com centenas ou milhares de comentários?
quebra-nozes de

2

sans SEQUENCE, funciona apenas no 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 se você prefere o mal ao bem:

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

0

E aqui está outra solução que funciona e usa uma tabela 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

Mas, novamente, não tenho certeza de que isso tenha desempenho.

Eu tenho um monte de respostas agora. Posso obter algumas votações e comentários para saber qual é o vencedor!

Obrigado a todos :-)


1
sua resposta é quase a mesma com depesz, basta remover o c.ID IN (1,3,2,4). de qualquer maneira, o seu é melhor, ele usa JOIN, tanto quanto possível, usa o modo ANSI SQL de associação, não use tabela vírgula. Eu deveria ter lido sua resposta com cuidado, estou tendo dificuldades para descobrir como usar o apelido das duas colunas. Primeiro, tentei o seguinte: (valores (1,1) como x (id, ordenação_ordem), (3,2), (2,3), (4,4)) como y. mas sem sucesso :-D sua resposta poderia ter me forneceu uma pista se eu li-o com cuidado :-)
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]

O unnest ainda não está embutido no 8.3, mas você pode criar um você mesmo (a beleza de qualquer *):

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

essa função pode funcionar em qualquer tipo:

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

Obrigado Michael, mas a função desagradável parece não existir para o meu PSQL e também não consigo encontrar nenhuma menção nos documentos. É 8.4 apenas?
quebra-nozes de

O unnest ainda não está embutido no 8.3, mas você pode implementar um. veja o código acima
Michael Buen

0

Leve melhoria em relação à versão que usa uma sequência, eu acho:

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)

aqui, [bbs] é a tabela principal que possui um campo chamado ids e ids é a matriz que armazena o comments.id.

passado no postgresql 9.6


você testou esta consulta?
lalithkumar

aqui, lembre-se, ids é um tipo de matriz, como {1,2,3,4}.
user6161156

0

Vamos ter uma impressão visual sobre o que já foi dito. Por exemplo, você tem uma tabela com algumas tarefas:

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

E você deseja ordenar a lista de tarefas por seu status. O status é uma lista de valores de sequência:

(processing, pending,  completed, deleted)

O truque é atribuir um valor a cada valor de status e ordenar a 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;

O que leva 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


-1

Concordo com todos os outros pôsteres que dizem "não faça isso" ou "SQL não é bom nisso". Se você deseja classificar por algum aspecto dos comentários, adicione outra coluna inteira a uma de suas tabelas para manter seus critérios de classificação e classificar por esse valor. por exemplo, "ORDER BY comments.sort DESC" Se você quiser classificá-las em uma ordem diferente todas as vezes, então ... SQL não será para você neste caso.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.