ЗАКАЗАТЬ ПО списку значений IN

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

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

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

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


person nutcracker    schedule 15.05.2009    source источник
comment
И я бы предпочел не создавать новую таблицу только для сортировки (несмотря на чистоту SQL).   -  person nutcracker    schedule 15.05.2009
comment
У меня сейчас куча ответов. Могу я получить несколько голосов и комментариев, чтобы я знал, кто из них победитель! Спасибо всем :-)   -  person nutcracker    schedule 15.05.2009


Ответы (17)


Вы можете сделать это довольно легко с помощью (введено в 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
person Community    schedule 15.05.2009
comment
@ user80168 Что делать, если в предложении IN тысячи значений? потому что я должен сделать это для тысяч записей - person kamal; 20.10.2016
comment
@kamal Для этого я использовал with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder. - person Noumenon; 24.03.2019

В 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.

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

  • Строковый литерал для передачи в массиве вместо Конструктор ARRAY может быть проще реализовать с некоторыми клиентами.

  • Для удобства (необязательно) скопируйте имя столбца, к которому мы присоединяемся (id в примере), чтобы мы могли присоединиться с помощью короткого предложения USING, чтобы получить в результате только один экземпляр столбца соединения.

Детальное объяснение:

person Erwin Brandstetter    schedule 17.02.2016
comment
Ошибка 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' - person Pipo; 08.08.2020
comment
@Pipo: Каким-то образом вам удалось объединить NULLNULL, что, очевидно, не является допустимым целочисленным значением. Это не связано с моим ответом. - person Erwin Brandstetter; 15.08.2020
comment
работает с psql cmdline, но не с odbc, который я использую, juste добавил его, если у кого-то такая же ошибка ... - person Pipo; 15.08.2020
comment
Это лучший ответ. - person Dex; 06.10.2020
comment
Это способ. Кстати, вы также можете сделать JOIN UNNEST(ARRAY['B','C','A']::text[]) WITH ORDINALITY t(id, ord) USING (id), если хотите упорядочить в соответствии со списком строк вместо целых чисел. - person vreyespue; 13.04.2021

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

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
person das oe    schedule 15.01.2012
comment
Список значений должен быть предоставлен дважды двумя разными способами. Не все так просто. Принятому ответу он нужен только один раз (даже если он будет более подробным). И это еще проще с современными Postgres (как показано в новых ответах). Кроме того, этот вопрос, похоже, все-таки касается Postgres. - person Erwin Brandstetter; 05.03.2016

Это можно сделать с помощью 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);
person a_horse_with_no_name    schedule 13.04.2015
comment
Это не повторяет весь список IN из предложения WHERE снова в предложении ORDER BY, что делает это лучшим ответом imho ... Теперь только чтобы найти что-то подобное для MySQL ... - person Stijn de Witt; 21.12.2015
comment
Мой любимый ответ, но обратите внимание, что array_position не работает с bigint, и вам нужно будет использовать: order by array_position(array[42,48,43], c.id::int);, что в некоторых случаях может привести к ошибкам. - person aaandre; 27.09.2019
comment
@aaandre Следующее приведение работает нормально (по крайней мере, в Postgres 12) array_position(array[42, 48, 43]::bigint[], c.id::bigint), поэтому нет необходимости усекать bigint до int. - person Vic; 01.05.2020

Думаю, так лучше:

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
person vantrung -cuncon    schedule 28.02.2012
comment
Я смог сделать это со связанными значениями, то есть: ... order by id=? desc, id=? desc, id=? desc, и, похоже, все работает нормально :-) - person KajMagnus; 15.03.2014
comment
Работает в postgres и кажется лучшим решением! - person Mike Szyndel; 08.10.2015
comment
Это решение помогло мне, но: Кто-нибудь исследовал, как это решение работает с точки зрения производительности? Он добавляет множественный порядок по пунктам. Поэтому он может (я еще не тестировал) экспоненциально замедляться с увеличением числа идентификаторов заказов? Любая информация по этому поводу будет принята с благодарностью! - person Fabian Schöner; 15.08.2016
comment
ОШИБКА: целевые списки могут содержать не более 1664 записей - ›при попытке выполнить длинный запрос ... - person Fatkhan Fauzi; 13.10.2016
comment
@ Manngo MS SQL. Не помню, какая версия. Может быть, это был 2012 год. - person biko; 08.05.2018
comment
У меня возникли проблемы с производительностью при использовании этого метода. У меня был заказ примерно с 850 идентификаторами, и Постгрес даже не ответил (вероятно, истекло время). Так что будьте осторожны, если у вас много идентификаторов. - person Laurent Van Winckel; 18.07.2019
comment
Работает в PostgreSQL 10.12 и, похоже, не зависит от реализации. - person Dmitriy Work; 14.07.2020

Другой способ сделать это в Postgres - использовать функцию idx.

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

Не забудьте сначала создать функцию idx, как описано здесь: http://wiki.postgresql.org/wiki/Array_Index

person Carl Mercier    schedule 13.01.2013
comment
Эта функция теперь доступна в расширении PostgreSQL: postgresql.org/docs /9.2/static/intarray.html Установите его с помощью CREATE EXTENSION intarray;. - person Alex Kahn; 28.08.2014
comment
Просто добавляю, что для пользователей Amazon RDS функция миграции ROR enable_extension позволит вам активировать ее, если пользователь вашего приложения является членом группы rds_superuser. - person Dave S.; 06.08.2015
comment
в PG 9.6.2 PG :: UndefinedFunction: ОШИБКА: функция idx (integer [], integer) не существует - person Yakob Ubaidi; 13.04.2017
comment
Спасибо, лучший ответ в сочетании с комментарием @ AlexKahn - person Andrew; 09.11.2017

В Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
person Clodoaldo Neto    schedule 26.03.2013
comment
Хм ... глючит, если position(id::text in '123,345,3,678'). Идентификатор 3 будет соответствовать идентификатору 345, не так ли? - person alanjds; 12.04.2014
comment
Я думаю, что вы правы, и тогда вам понадобится как начальный, так и конечный разделители, например: порядок по позиции (',' || id :: text || ',' in ', 1,3,2,4, ') - person Michael Rush; 10.06.2014
comment
@MichaelRush Это тоже не работает на 100%. Например, если есть (11, 1), сначала будет отображаться 1. - person Dex; 06.10.2020

Изучив это еще немного, я нашел это решение:

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

Однако это кажется довольно многословным и может иметь проблемы с производительностью с большими наборами данных. Кто-нибудь может прокомментировать эти вопросы?

person nutcracker    schedule 15.05.2009
comment
Конечно, я могу их прокомментировать. Есть вещи, в которых SQL хорош, и есть вещи, в которых он не силен. SQL не подходит для этого. Просто отсортируйте результаты на том языке, с которого вы делаете запросы; это избавит вас от многих стенаний и скрежета зубов. SQL - это язык, ориентированный на наборы, а наборы не являются упорядоченными коллекциями. - person kquinn; 15.05.2009
comment
Хммм ... Это основано на личном опыте и тестировании? Мой проверенный опыт показывает, что это довольно эффективный метод заказа. (Однако принятый ответ в целом лучше, поскольку он исключает предложение IN (...)). Помните, что для любого разумного размера набора результатов получение набора должно быть дорогостоящей частью. Когда количество записей сокращается до нескольких сотен или меньше, сортировка становится тривиальной. - person dkretz; 05.06.2009
comment
Что, если в предложении IN тысячи значений? потому что я должен сделать это для тысяч записей. - person kamal; 20.10.2016

Для этого, я думаю, вам, вероятно, следует иметь дополнительную таблицу ORDER, которая определяет сопоставление идентификаторов для заказа (эффективно выполняя то, что сказал ваш ответ на ваш собственный вопрос), которую вы затем можете использовать в качестве дополнительного столбца для вашего выбора, который затем можно будет продолжить сортировку.

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

person Paul Sonier    schedule 15.05.2009
comment
Кажется, это правильный способ сделать это. Однако я бы хотел создать эту таблицу заказов на лету. В одном из ответов я предложил использовать постоянную таблицу. Будет ли это эффективным, когда я имею дело с сотнями или тысячами комментариев? - person nutcracker; 15.05.2009

без ПОСЛЕДОВАТЕЛЬНОСТИ, работает только на 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
person Michael Buen    schedule 15.05.2009

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,')
person Hafthor    schedule 15.05.2009

А вот еще одно решение, которое работает и использует постоянную таблицу (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

Но опять же я не уверен, что это работоспособно.

У меня сейчас куча ответов. Могу ли я получить несколько голосов и комментариев, чтобы я знал, кто из них победитель!

Спасибо всем :-)

person nutcracker    schedule 15.05.2009
comment
ваш ответ почти такой же, как и с depesz, просто удалите c.ID IN (1,3,2,4). в любом случае его лучше, он использует JOIN, по возможности использует способ соединения ANSI SQL, не использует таблицу запятых. Я должен был внимательно прочитать ваш ответ, мне трудно понять, как связать два столбца, сначала я попробовал это: (values ​​(1,1) as x (id, sort_order), (3,2), (2,3), (4,4)) как y. но безрезультатно :-D ваш ответ мог бы дать мне ключ к разгадке, если бы я внимательно его прочитал :-) - person Michael Buen; 15.05.2009

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
person Michael Buen    schedule 15.05.2009
comment
Спасибо, Майкл, но функции unnest для моего PSQL, похоже, не существует, и я также не могу найти упоминания о ней в документации. Это только 8.4? - person nutcracker; 15.05.2009
comment
unnest еще не встроен в 8.3, но вы можете реализовать его самостоятельно. см. код выше - person Michael Buen; 15.05.2009

Я думаю, небольшое улучшение по сравнению с версией, в которой используется последовательность:

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;
person Community    schedule 05.06.2009

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 - это массив, в котором хранится comment.id.

прошло в postgresql 9.6

person user6161156    schedule 06.06.2017
comment
вы проверяли этот запрос? - person lalithkumar; 06.06.2017
comment
здесь, помните, ids - это тип массива, например {1,2,3,4}. - person user6161156; 06.06.2017

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

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

person Manuel    schedule 11.07.2019

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

person Trey    schedule 15.05.2009