按IN值列表排序


165

我在PostgreSQL 8.3中有一个简单的SQL查询,可获取大量注释。我在子句中为构造提供了一个排序的值列表:INWHERE

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

这会以任意顺序返回评论,在我看来,这些评论恰好是ID 1,2,3,4

我希望结果行的排序类似于IN构造中的列表:(1,3,2,4)
如何实现呢?


而且我宁愿不要为排序而创建新表(尽管SQL纯粹)。
胡桃夹子》

2
我现在有很多答案。我能得到一些投票和评论,以便我知道哪个是赢家!谢谢大家:-)
胡桃夹子

Answers:


106

您可以很容易地使用PostgreSQL(在PostgreSQL 8.2中引入)VALUES(),()。

语法如下:

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如果IN子句中有数千个值怎么办?因为我必须要做成千上万的记录
kamal 16-10-20

@kamal我已经使用过with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder
Noumenon

66

只是因为它是如此难以找到并且必须传播:在mySQL中,这可以简单得多,但是我不知道它是否可以在其他SQL中工作。

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

3
值列表必须以两种不同的方式提供两次。没那么简单。接受的答案仅需要一次(即使以更冗长的方式)。使用现代Postgres甚至更简单(如最新答案所示)。而且,这个问题似乎毕竟与Postgres有关。
Erwin Brandstetter

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

54

在Postgres 9.4或更高版本中,这可能是最简单,最快的

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • 使用新的WITH ORDINALITY,那@a_horse已经提到

  • 我们不需要子查询,我们可以像表一样使用set-returning函数。

  • 使用某些字符串而不是ARRAY构造函数传递给数组的字符串文字可能更易于在某些客户端上实现。

详细说明:


46

我认为这样更好:

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
我能够使用绑定值来执行此操作,即:... order by id=? desc, id=? desc, id=? desc似乎运行良好:-)
KajMagnus

在postgres中工作,似乎是最好的解决方案!
Mike Szyndel 2015年

该解决方案为我解决了问题,但是:是否有人研究了该解决方案在性能方面的表现?它确实添加了多个order by子句。因此,随着订单ID数量的增加,它可能(我还没有对其进行测试)变得越来越慢?任何信息,将不胜感激!
FabianSchöner,

1
错误:目标列表最多可以有1664项- >当您尝试运行长的查询...
Fatkhan法兹

@Manngo MS SQL。不记得哪个版本。可能是2012
。– biko,

42

使用Postgres 9.4,可以缩短操作时间:

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;

或没有派生表的情况下更加紧凑:

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

无需为每个值手动分配/保持位置。

使用Postgres 9.6可以使用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);

使用CTE以便只需要指定一次值列表。如果那并不重要,也可以写成:

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

这不会再次在该子句中重复该子句的整个IN列表,这使该问题成为最佳答案……现在只为MySQL找到类似的东西……WHEREORDER BY
Stijn de Witt 2015年

1
我最喜欢的答案,但请注意array_position不适用于bigint,并且您需要强制转换:order by array_position(array[42,48,43], c.id::int);在某些情况下可能会导致错误。
aaandre

1
@aaandre以下强制转换工作正常(至少在Postgres 12中有效)array_position(array[42, 48, 43]::bigint[], c.id::bigint),因此无需截断bigintint
维克

29

在Postgres中执行此操作的另一种方法是使用该idx函数。

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

不要忘记先创建idx函数,如下所述:http : //wiki.postgresql.org/wiki/Array_Index


11
现在,该功能在PostgreSQL随附的扩展程序中可用:postgresql.org/docs/9.2/static/intarray.html使用进行安装CREATE EXTENSION intarray;
Alex Kahn 2014年

1
只是进一步说明一下,对于Amazon RDS用户,enable_extension只要您的应用程序用户是该rds_superuser组的成员,ROR迁移功能就可以使您激活它。
Dave S.

在PG 9.6.2中PG :: UndefinedFunction:错误:函数idx(integer [],整数)不存在
Yakob Ubaidi

谢谢,结合@AlexKahn的评论时的最佳答案
Andrew

21

在Postgresql中:

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

2
呜呜......它的错误,如果position(id::text in '123,345,3,678')。id 3将在id之前匹配345,不是吗?
alanjds 2014年

4
我认为您是对的,可能需要同时包含开始和结束定界符,例如:在',1,3,2,4,中按position(','|| id :: text ||',' ')
Michael Rush

3

经过研究,我找到了这个解决方案:

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

但是,这似乎很冗长,并且对于大型数据集可能存在性能问题。谁能评论这些问题?


7
当然,我可以对它们发表评论。SQL有一些优点,而某些方面却不是。SQL并不擅长于此。只需使用您要使用的查询语言来对结果进行排序;它将为您节省很多牙齿的磨牙和咬牙。SQL是一种面向集合的语言,并且集合不是有序集合。
kquinn

嗯...那是基于个人经验和测试吗?我经过测试的经验是,这是一种非常有效的订购技术。(但是,可接受的答案总体上更好,因为它消除了“ IN(...)”子句)。请记住,对于任何合理的结果集大小,派生集合应该是昂贵的部分。一旦记录减少到几百条或更少,排序就变得微不足道了。
dkretz

如果IN子句中有数千个值怎么办?因为我必须要做数千条记录。
卡马尔

2

为此,我认为您可能应该有一个附加的“ ORDER”表,该表定义ID到订单的映射(有效地执行您对自己的问题的回答),然后可以将其用作所选内容的附加列然后可以继续排序。

这样,您可以在数据库中显式地描述所需的顺序。


这似乎是正确的方法。但是我想即时创建该订购表。我建议在答案之一中使用常量表。当我处理数百或数千条评论时,这会表现出色吗?
胡桃夹子》

2

sans SEQUENCE,仅适用于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

或者,如果您更喜欢邪恶而不是善良:

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

0

这是另一个可行的解决方案,可以使用常量表(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

但是同样,我不确定这是否表现出色。

我现在有很多答案。我能得到一些投票和评论,以便我知道哪个是赢家!

谢谢大家:-)


1
您的答案与depesz几乎相同,只需删除c.ID IN(1、3、2、4)。无论如何他都更好,他使用JOIN,尽可能使用ANSI SQL的联接方式,不要使用表逗号表。我应该仔细阅读您的答案,我很难弄清楚如何为这两列取别名,首先我尝试这样做:(values(1,1)as x(id,sort_order),(3,2), (2,3),(4,4))为y。但无济于事:-D如果我认真阅读它,您的回答可能为我提供了一个提示:-)
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;

[编辑]

unnest尚未在8.3中内置,但是您可以自己创建一个(任何一个的美*):

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

该函数可以以任何类型工作:

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

谢谢Michael,但是我的PSQL似乎不存在unnest函数,我在文档中也找不到任何提及。只有8.4吗?
胡桃夹子

unnest尚未在8.3中内置,但是您可以自己实现。参见上面的代码
Michael Buen

0

我认为使用序列的版本略有改进:

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)

在这里,[bbs]是具有名为ids的字段的主表,而ids是存储comments.id的数组。

在PostgreSQL 9.6中传递


您是否测试过此查询?
lalithkumar

在这里,请记住,id是数组类型,例如{1,2,3,4}。
user6161156

0

让我们对已经说过的内容有一个视觉印象。例如,您有一个包含一些任务的表:

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

您想按其状态对任务列表进行排序。状态是字符串值的列表:

(processing, pending,  completed, deleted)

技巧是给每个状态值一个整数并按数字顺序排列列表:

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;

这导致:

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

信用@ user80168


-1

我同意所有其他张贴者的话,即“不要这样做”或“ SQL不能做到这一点”。如果要按注释的某些方面进行排序,则将另一个整数列添加到一个表中,以保存您的排序条件并按该值进行排序。例如“ ORDER BY comments.sort DESC”如果您希望每次都以不同的顺序对它们进行排序,那么...在这种情况下,SQL将不适合您。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.