按 IN 值列表排序

Posted

技术标签:

【中文标题】按 IN 值列表排序【英文标题】:ORDER BY the IN value list 【发布时间】:2010-10-26 08:56:19 【问题描述】:

我在 PostgreSQL 8.3 中有一个简单的 SQL 查询,它抓取了一堆 cmets。我在WHERE 子句中为IN 构造提供了一个排序的 值列表:

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

这会以任意顺序返回 cmets,在我看来,这恰好是像 1,2,3,4 这样的 id。

我希望结果行按照IN 构造中的列表进行排序:(1,3,2,4)。 如何实现?

【问题讨论】:

而且我不希望仅仅为了排序而创建一个新表(尽管 SQL 纯度很高)。 我现在有很多答案。我可以得到一些投票和 cmets,这样我就知道哪个是赢家!谢谢大家:-) 【参考方案1】:

在 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 是在 Postgres 9.4 中引入的。

不需要子查询,我们可以像表一样直接使用set-returning函数。 (又名“表函数”。)

在数组中传递字符串而不是 ARRAY constructor 可能更容易在某些客户端上实现。

为方便起见(可选),复制我们要连接的列名(示例中为id),这样我们就可以使用简短的USING 子句连接,从而在结果中只获取连接列的单个实例.

适用于任何输入类型。如果您的键列类型为 text,请提供类似 'foo,bar,baz'::text[] 的内容。

详细解释:

PostgreSQL unnest() with element number

【讨论】:

不幸的是我的 ODBC 错误:ODBC ERROR: <4>, Inter code: <7> Native Err#=1 , SQLSTATE=42601, Error_Info='ERROR: syntax error at or near "NULLNULL"; Error while preparing parameters' @Pipo:不知何故,您设法连接了“NULLNULL”,这显然不是合法的整数值。这与我的回答无关。 适用于 psql cmdline,但不适用于我使用的 odbc,如果有人遇到相同的错误,请添加它... 这是最好的答案。 就是这样。顺便说一句,如果您想根据字符串列表而不是整数进行排序,您也可以使用JOIN UNNEST(ARRAY['B','C','A']::text[]) WITH ORDINALITY t(id, ord) USING (id)【参考方案2】:

在 Postgresql 中:

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

【讨论】:

哼...如果position(id::text in '123,345,3,678') 会出错。 id 3 将在 id 345 之前匹配,不是吗? 我认为您是对的,然后需要同时具有开始和结束分隔符,例如: order by position(','||id::text||',' in ', 1,3,2,4,') @MichaelRush 这似乎也不能 100% 工作。例如,如果有 (11, 1),则首先显示 1。【参考方案3】:

使用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);

【讨论】:

这不会在WHERE 子句中再次重复WHERE 子句中的整个IN 列表,这使得这是最好的答案恕我直言......现在只为mysql 找到类似的东西... 我最喜欢的答案,但请注意,array_position 不适用于 bigint,您需要转换:order by array_position(array[42,48,43], c.id::int);,这在某些情况下可能会导致错误。 @aaandre 以下转换工作正常(至少在 Postgres 12 中)array_position(array[42, 48, 43]::bigint[], c.id::bigint),因此无需将 bigint 截断为 int【参考方案4】:

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

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

【讨论】:

【参考方案5】:
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 是存储 cmets.id 的数组。

在 postgresql 9.6 中通过

【讨论】:

你测试过这个查询吗? 这里,记住,ids 是一个数组类型,例如,1,2,3,4。【参考方案6】:

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

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

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

【讨论】:

这个功能现在可以在 PostgreSQL 附带的扩展中使用:postgresql.org/docs/9.2/static/intarray.html 使用CREATE EXTENSION intarray; 安装它。 只要您的应用程序用户是rds_superuser 组的成员,就进一步增加,对于Amazon RDS 用户,ROR 迁移功能enable_extension 将允许您激活此功能。 in PG 9.6.2 PG::UndefinedFunction: ERROR: function idx(integer[], integer) 不存在 谢谢,结合@AlexKahn 的评论的最佳答案【参考方案7】:

我认为这种方式更好:

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

【讨论】:

我能够使用绑定值来做到这一点,即:... order by id=? desc, id=? desc, id=? desc,它似乎工作正常:-) 在 postgres 中工作,似乎是最好的解决方案! 这个解决方案对我有用,但是:有没有人研究过这个解决方案在性能方面的表现?它确实添加了多个 order by 子句。因此它可能(我还没有测试过)随着订单ID数量的增加而呈指数增长?任何有关这方面的信息将不胜感激! 错误:目标列表最多可以有 1664 个条目 -> 当您尝试运行长查询时... @Manngo MS SQL。不记得是哪个版本了。可能是 2012 年。【参考方案8】:

就因为太难找了,不得不传播:in mySQL this can be done much simpler,不过不知道其他SQL能不能用。

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

【讨论】:

必须以两种不同的方式提供两次的值列表。没那么简单。接受的答案只需要 once (即使以更冗长的方式)。现代 Postgres 甚至更简单(如更新的答案所示)。此外,这个问题似乎毕竟是关于 Postgres 的。 ERROR: cannot pass more than 100 arguments to a function【参考方案9】:

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

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;

【讨论】:

【参考方案10】:

您可以使用(在 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

【讨论】:

@user80168 如果 IN 子句中有数千个值怎么办?因为我必须为数千条记录做这件事 @kamal 为此我使用了with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder【参考方案11】:
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 中内置,但您可以自己实现。见上面的代码【参考方案12】:

无序列,仅适用于 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

【讨论】:

【参考方案13】:

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

但我又不确定这是否有效。

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

谢谢大家 :-)

【讨论】:

你的答案和depesz差不多,只是去掉c.ID IN (1,3,2,4)。反正他的比较好,他用JOIN,尽量用ANSI SQL的连接方式,不要用表逗号表。我应该仔细阅读您的答案,我很难弄清楚如何给这两列起别名,首先我尝试了这个:(values(1,1)作为x(id,sort_order),(3,2), (2,3), (4,4)) 作为 y。但无济于事:-D如果我仔细阅读你的回答可能会给我一个线索:-)【参考方案14】:
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,')

【讨论】:

【参考方案15】:

我同意所有其他说“不要那样做”或“SQL 不擅长那样”的海报。如果您想按 cmets 的某个方面进行排序,则将另一个整数列添加到您的一个表中以保存您的排序标准并按该值排序。例如“ORDER BY cmets.sort DESC” 如果您想每次都以不同的顺序对它们进行排序,那么...在这种情况下,SQL 不适合您。

【讨论】:

【参考方案16】:

为此,我认为您可能应该有一个额外的“ORDER”表,它定义了 ID 到订单的映射(有效地按照您对自己问题的回答),然后您可以将其用作附加列您的选择,然后您可以对其进行排序。

通过这种方式,您可以在数据库中明确描述您想要的排序,它应该在哪里。

【讨论】:

这似乎是正确的做法。但是,我想即时创建该订购表。我建议在其中一个答案中使用常量表。当我处理成百上千的 cmets 时,这是否会表现出色?【参考方案17】:

在研究了更多之后,我发现了这个解决方案:

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

但是,这似乎相当冗长,并且可能在大型数据集上存在性能问题。 任何人都可以对这些问题发表评论吗?

【讨论】:

当然,我可以评论他们。 SQL 有擅长的事情,也有不擅长的事情。 SQL 不擅长这一点。只需使用您进行查询的任何语言对结果进行排序;它会让你免于痛苦和咬牙切齿。 SQL 是面向集合的语言,集合不是有序集合。 嗯...这是基于个人经验和测试吗?我的测试经验是,这是一种非常有效的订购技术。 (但是,接受的答案总体上更好,因为它消除了“IN (...)”子句)。请记住,对于任何合理的结果集大小,派生该集应该是昂贵的部分。一旦减少到几百条记录或更少,排序就变得微不足道了。 如果IN 子句中有数千个值怎么办?因为我必须为成千上万的记录做这件事。

以上是关于按 IN 值列表排序的主要内容,如果未能解决你的问题,请参考以下文章

Magento列表按属性排序按属性排序而不是值排序

数组列表按布尔值排序,然后按日期 JavaScript / TypeScript

C#按以下值对排序列表进行分组

按属性值对对象列表进行排序[重复]

按属性名称(字符串值)排序列表? [复制]

Android-java-如何按对象内的某个值对对象列表进行排序