ПОРЯДОК ВЫПОЛНЕНИЯ IN IN

166

У меня есть простой SQL-запрос в PostgreSQL 8.3, который собирает кучу комментариев. Я предоставляю отсортированный список значений для INконструкции в WHEREпредложении:

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

Это возвращает комментарии в произвольном порядке, который в моем случае похож на id 1,2,3,4.

Я хочу , чтобы получившиеся строки , упорядоченные как список в INконструкции: (1,3,2,4).
Как этого добиться?

щелкунчик
источник
И я бы предпочел не создавать новую таблицу только для сортировки (несмотря на чистоту SQL).
Щелкунчик
2
У меня есть куча ответов сейчас. Могу ли я получить голосование и комментарии, чтобы я знал, кто победит! Спасибо всем :-)
Щелкунчик

Ответы:

107

Вы можете сделать это довольно легко с (представлен в 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 Для этого я использовал with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Ноумен
66

Просто потому, что его так сложно найти и нужно распространять: в MySQL это можно сделать гораздо проще , но я не знаю, работает ли он в другом SQL.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
Das OE
источник
3
Список значений должен быть представлен дважды , двумя разными способами. Не все так просто. Принятый ответ нужен только один раз (даже если более многословно). И это еще проще с современными Postgres (как показано в новых ответах). Кроме того, этот вопрос, кажется, в конце концов, о Postgres.
Эрвин Брандштеттер
8
ERROR: cannot pass more than 100 arguments to a function
Браулиобо
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 уже упоминалось .

  • Нам не нужен подзапрос, мы можем использовать функцию возврата набора, как таблицу.

  • Строковый литерал, передаваемый в массив вместо конструктора 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 и кажется лучшим решением!
Майк Шиндель
Это решение помогло мне, но: кто-нибудь исследовал, как это решение влияет на производительность? Это добавляет множественный порядок по пунктам. Следовательно, может (я еще не тестировал) экспоненциально становиться медленнее с увеличением количества идентификаторов ордеров? Любая информация по этому вопросу будет принята с благодарностью!
Фабиан Шенер
1
ОШИБКА: целевые списки могут содержать не более 1664 записей -> при попытке выполнить длинный запрос ...
Фатхан Фаузи
@Manngo MS SQL. Не могу вспомнить, какая версия. Возможно, это был 2012 год.
Бико
43

С 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);
a_horse_with_no_name
источник
Это не повторяет весь INсписок из WHEREпредложения снова в ORDER BYпредложении, что делает это лучшим ответом imho ... Теперь только чтобы найти что-то похожее для MySQL ...
Stijn de Witt
1
Мой любимый ответ, но обратите внимание, что array_position не работает с bigint, и вам необходимо выполнить приведение: order by array_position(array[42,48,43], c.id::int);которое может привести к ошибкам в некоторых случаях.
aaandre
1
@aaandre Следующее приведение работает нормально (по крайней мере array_position(array[42, 48, 43]::bigint[], c.id::bigint), в Postgres 12) , поэтому не нужно усекаться bigintдо int.
Вик
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;.
Алекс Кан
1
Для пользователей Amazon RDS добавим еще больше, и функция миграции ROR enable_extensionпозволит вам активировать эту функцию , пока пользователь вашего приложения является членом rds_superuserгруппы.
Дейв С.
в PG 9.6.2 PG :: UndefinedFunction: ERROR: функция idx (целое число [], целое число) не существует
Якоб
Спасибо, лучший ответ в сочетании с комментарием @ AlexKahn
Эндрю
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'). Идентификатор 3будет соответствовать перед идентификатором 345, не так ли?
alanjds
4
Я думаю, что вы правы, и тогда вам понадобится начальный и конечный разделители, например: order by position (',' || id :: text || ',' in ', 1,3,2,4, ')
Майкл Раш
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», которая определяет отображение идентификаторов в порядке (эффективно выполняющих то, что сказал ваш ответ на ваш собственный вопрос), которую вы затем можете использовать в качестве дополнительного столбца по вашему выбору, который Вы можете сортировать дальше.

Таким образом, вы явно описываете порядок, который вы хотите в базе данных, где это должно быть.

Пол Сонье
источник
Это кажется правильным способом сделать это. Однако я хотел бы создать эту таблицу заказов на лету. Я предложил использовать постоянную таблицу в одном из ответов. Будет ли это эффективно, когда я имею дело с сотнями или тысячами комментариев?
Щелкунчик
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,')
Hafthor
источник
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, не используйте таблицу запятой. Я должен был внимательно прочитать ваш ответ, мне трудно понять, как псевдоним двух столбцов, сначала я попробовал это: (значения (1,1) как x (id, sort_order), (3,2), (2,3), (4,4)) как у. но безрезультатно: - Ваш ответ мог бы дать мне подсказку, если бы я внимательно его прочитал :-)
Майкл Буэн
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
Майкл Буэн
источник
Спасибо, Майкл, но для моего PSQL, похоже, нет самой плохой функции, и я не могу найти упоминания об этом в документации. Это только 8.4?
Щелкунчик
unnest еще не встроен в 8.3, но вы можете реализовать его самостоятельно. см. код выше
Майкл Буэн
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

user6161156
источник
Вы проверяли этот запрос?
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

Credit @ user80168

Manuel
источник
-1

Я согласен со всеми другими авторами, которые говорят «не делай этого» или «SQL не хорош в этом». Если вы хотите отсортировать по некоторому фасету комментариев, добавьте еще один целочисленный столбец в одну из ваших таблиц, чтобы сохранить критерии сортировки и отсортировать по этому значению. например, "ORDER BY comments.sort DESC" Если вы хотите сортировать их каждый раз в другом порядке, тогда ... SQL в этом случае не подойдет вам.

тройка
источник