Упорядочение по порядку значений в предложении SQL IN ()


154

Мне интересно, есть ли возможность (возможно, лучший способ) упорядочить по порядку значений в предложении IN ().

Проблема в том, что у меня есть 2 запроса, один из которых получает все идентификаторы, а второй - всю информацию. Первый создает порядок идентификаторов, по которым я хочу, чтобы второй упорядочил. Идентификаторы помещаются в предложение IN () в правильном порядке.

Так что это будет что-то вроде (очень упрощенно):

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

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

Проблема заключается в том, что второй запрос не возвращает результаты в том же порядке, в котором идентификаторы помещены в предложение IN ().

Одно решение, которое я нашел, состоит в том, чтобы поместить все идентификаторы во временную таблицу с автоматически возрастающим полем, которое затем присоединяется ко второму запросу.

Есть ли лучший вариант?

Примечание. Поскольку первый запрос выполняется «пользователем», а второй - в фоновом процессе, невозможно объединить запрос 2 в 1, используя подзапросы.

Я использую MySQL, но думаю, что было бы полезно отметить, какие есть варианты и для других БД.

Ответы:


186

Используйте FIELD()функцию MySQL :

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

Это будет делать именно то, что вы хотите, если вы вставите идентификаторы в IN()предложение и FIELD()функцию в том же порядке.


3
@ Войто сортировка по произвольному порядку медленная по определению. Это делает все возможное, поскольку нет гарантии, что 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

11

Два решения, которые приходят на ум:

  1. order by case id when 123 then 1 when 456 then 2 else null end asc

  2. order by instr(','||id||',',',123,456,') asc

( instr()от Oracle; может быть, у вас есть locate()или charindex()или что-то в этом роде)


Для MySQL можно также использовать FIELD_IN_SET (): dev.mysql.com/doc/refman/5.0/en/…
Darryl Hein


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.

Примечание . Второй столбец в созданной таблице (orderTbl.orderIdx) необходим при запросе наборов записей больше 100 или около того. Изначально у меня не было столбца orderIdx, но я обнаружил, что с наборами результатов больше 100 мне пришлось явно сортировать по этому столбцу; в SQL Server Express 2014 в любом случае.


Этот вопрос касается MySQL ... не уверен, что ваш запрос будет работать в MySQL.
Дэррил Хейн

2
@ Darryl, это не так. Но этот пост является лучшим результатом, когда вы гугляете, как упорядочить по предложению IN, поэтому я решил, что я тоже могу опубликовать его здесь, и общая методология применима ко всем SQL-серверам, совместимым с ANSI (просто замените оператор 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. Быстро и просто. Спасибо.
Менахем

4

Предложение IN описывает набор значений, а наборы не имеют порядка.

Ваше решение с объединением и последующим заказом по display_orderстолбцу является наиболее правильным решением; все остальное, вероятно, является хаком, специфичным для СУБД (или делает что-то с функциями OLAP в стандартном SQL). Конечно, объединение является наиболее переносимым решением (хотя генерирование данных со display_orderзначениями может быть проблематичным). Обратите внимание, что вам может потребоваться выбрать столбцы заказа; это было требованием в стандартном SQL, хотя я полагаю, что это было смягчено, как правило, некоторое время назад (может быть, так же давно, как SQL-92).



2

Для Oracle работает решение Джона с использованием функции instr (). Вот немного другое решение, которое сработало - 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

Моей первой мыслью было написать один запрос, но вы сказали, что это невозможно, потому что один запускается пользователем, а другой - в фоновом режиме. Как вы храните список идентификаторов для передачи от пользователя к фоновому процессу? Почему бы не положить их во временную таблицу с колонкой для обозначения порядка.

Так как насчет этого:

  1. Бит пользовательского интерфейса запускается и вставляет значения в новую таблицу, которую вы создаете. Было бы вставить идентификатор, должность и какой-то идентификатор номера задания)
  2. Номер задания передается в фоновый процесс (вместо всех идентификаторов)
  3. Фоновый процесс делает выбор из таблицы на шаге 1, и вы присоединяетесь, чтобы получить другую необходимую вам информацию. Используется номер задания в предложении WHERE и порядок по столбцу позиции.
  4. Фоновый процесс после завершения удаляется из таблицы на основе идентификатора задания.

1

Я думаю, что вам нужно хранить свои данные таким образом, чтобы вы просто выполняли объединение, и оно было идеальным, поэтому никаких взломов и сложных вещей не происходит.

У меня есть, например, «Недавно воспроизведенный» список идентификаторов треков, на SQLite я просто делаю:

SELECT * FROM recently NATURAL JOIN tracks;

1
О, я бы хотел, чтобы жизнь была такой простой :)
Дэррил Хейн

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 запрос с подзапросами.
Дэррил Хейн
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.