按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按任意顺序排序很慢。这样做是最好的,因为不能保证IN和FIELD参数相等。通过使用这些附加知识,可以更快地在程序代码中执行此操作。当然,如果您考虑服务器性能,那么将这种负担放在客户端而不是服务器上会更明智。
— 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.orderKey,orderTbl.orderIndex通过orderKey,orderIndex
— 彼得·

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.