按SQL IN()子句中的值顺序排序


154

我想知道是否存在按IN()子句中的值顺序进行排序的方法(可能是一种更好的方法)。

问题是我有2个查询,一个查询获取所有ID,第二个查询获取所有信息。第一个创建我要第二个排序的ID的顺序。这些ID以正确的顺序放入IN()子句中。

所以它就像(极其简化):

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

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

问题在于第二个查询不会以将ID放入IN()子句的顺序来返回结果。

我发现的一种解决方案是将所有ID放入具有自动递增字段的临时表中,然后将其加入第二个查询中。

有更好的选择吗?

注意:由于第一个查询是“由用户”运行的,第二个查询是在后台进程中运行的,因此无法使用子查询将2合并为1。

我正在使用MySQL,但我想让它指出其他数据库也有哪些选项可能会很有用。

Answers:


186

使用MySQL的FIELD()功能:

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

FIELD() 将返回等于第一个参数的第一个参数的索引(第一个参数本身除外)。

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

将返回1

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

将返回3

如果将id 以相同的顺序粘贴到IN()子句和FIELD()函数中,这将完全满足您的要求。


3
通过定义,@ Vojto按任意顺序排序很慢。这样做是最好的,因为不能保证INFIELD参数相等。通过使用这些附加知识,可以更快地在程序代码中执行此操作。当然,如果您考虑服务器性能,那么将这种负担放在客户端而不是服务器上会更明智。
ivan_pozdeev

1
那又如何sqlite呢?
fnc12

15

请参阅以下如何获取排序的数据。

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



6

如果要使用MS SQL Server 2008+中查询输入的值对查询进行任意排序,可以通过动态创建表并进行联接(使用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

如果将VALUES语句替换为具有相同功能的其他东西,但在ANSI SQL中,则此方法应适用于任何SQL数据库。

注意: 查询大于100左右的记录集时,在创建的表中的第二列(orderTbl.orderIdx)是必需的。我最初没有orderIdx列,但是发现结果集大于100时,我必须按该列进行显式排序。无论如何都可以在SQL Server Express 2014中使用。


这个问题与MySQL有关...不确定您的查询将在MySQL中运行。
Darryl Hein

2
@Darryl,不会。但是,当您在Google上如何通过IN子句对Google进行排序时,这篇文章是最高的结果,因此我想我也可以在此处发布它,并且一般方法适用于所有符合ANSI的SQL服务器(只需将VALUES语句替换为标准创建表的方式)。
伊恩

请注意,这个工作对我来说,但只有在我替换为参考orderTbl.orderKeyorderTbl.orderIndex通过orderKeyorderIndex
彼得·

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)

真的很棒


在Oracle上为我工作。快捷方便。谢谢。
Menachem

4

IN子句描述一组值,而这些组没有顺序。

通过联接然后在display_order列上排序的解决方案是最接近正确的解决方案。其他任何事情都可能是特定于DBMS的黑客(或者正在使用标准SQL中的OLAP函数做一些事情)。当然,联接是最接近可移植的解决方案(尽管使用display_order值生成数据可能会有问题)。请注意,您可能需要选择排序列。过去这曾经是标准SQL的要求,但我相信它通常在不久前(也许早在SQL-92之前)就放宽了。


2

对于Oracle,使用instr()函数的John解决方案有效。这是可行的稍微不同的解决方案- SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)



2

我只是尝试通过MS SQL Server来执行此操作,而我们没有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

请注意,我也允许重复。


1

我的第一个想法是编写一个查询,但是您说这是不可能的,因为一个由用户运行,另一个由后台运行。您如何存储ID列表以从用户传递到后台进程?为什么不将它们放在带有表的临时表中以表示订单。

那么呢:

  1. 用户界面位运行,并将值插入到您创建的新表中。它将插入ID,职位和某种工作编号标识符)
  2. 作业编号将传递给后台进程(而不是所有ID)
  3. 后台进程从步骤1中的表中进行选择,然后您可以加入以获取所需的其他信息。它使用WHERE子句中的作业编号并按位置列进行排序。
  4. 后台进程完成后,将根据作业标识符从表中删除。

1

我认为您应该设法以一种简单的方式进行连接就可以存储数据,并且这种连接将是完美的,因此不会发生黑客和复杂的事情。

例如,我在SQLite上有一个曲目ID的“最近播放”列表,我只是这样做:

SELECT * FROM recently NATURAL JOIN tracks;

1
噢,我希望生活这么简单:)
Darryl Hein

0

试一下:

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

WHERE可能需要进行一些调整才能使相关子查询正常工作,但是基本原理应该是正确的。


请参见注释,并且如上所述,查询示例已大大简化,因此我无法将它们与子查询合并为1个查询。
达里尔·海因
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.