ЗАМОВИТИ за списком значень IN


165

У мене є простий запит 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).
Як цього досягти?


І я вважаю за краще не створювати нову таблицю лише для сортування (незважаючи на чистоту SQL).
Лускунчик

2
Зараз у мене є маса відповідей. Чи можу я отримати голосування та коментарі, щоб я знав, хто переможець! Дякую всім :-)
Лускунчик

Відповіді:


106

Ви можете це зробити досить легко за допомогою (введеного в 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

@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')

3
Список значень повинен бути наданий двічі двома різними способами. Не так просто. Прийнята відповідь потрібна лише один раз (навіть якщо більш докладно). І це ще простіше з сучасними Postgres (як це демонструється в нових відповідях). Також це питання, мабуть, стосується Postgres.
Ервін Брандстеттер

8
ERROR: cannot pass more than 100 arguments to a function
brauliobo

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

Працює в постграфах і, здається, найкраще рішення!
Майк Шендел

Це рішення зробило для мене хитрість, але: Хто-небудь досліджував, як це рішення спричинює ефективність роботи? Це додає кратний порядок за допомогою пунктів. Тому може (я ще не тестував це) повільніше експоненціально збільшуватись із збільшенням кількості замовлень? Будь-яка інформація про це буде дуже вдячна!
Фабіан Шьонер

1
ПОМИЛКА: цільові списки можуть містити не більше 1664 записів -> при спробі запустити довгий запит ...
Fatkhan Fauzi

@Manngo MS SQL. Не можу згадати, яка версія. Можливо, були 2012.
biko

42

З 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);

Це не повторює весь 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 Наступний лиття працює нормально (в Postgres 12 , по крайней мере) array_position(array[42, 48, 43]::bigint[], c.id::bigint), тому немає необхідності зрізаних 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: ПОМИЛКА: функція idx (integer [], integer) не існує
Якоб Убайді

Дякую, найкраща відповідь у поєднанні з коментарем @ 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
Я думаю, ви маєте рацію, і вам потрібно мати роздільник для початку та кінця, можливо, як: порядку по позиції (',' || 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пункті є тисячі значень ? тому що я маю це зробити для тисяч записів.
kamal

2

Для цього я думаю, ви, мабуть, повинні мати додаткову таблицю "ЗАМОВЛЕННЯ", яка визначає відображення ідентифікаторів для замовлення (фактично виконуючи те, що сказала відповідь на ваше власне запитання), яку ви потім можете використовувати як додатковий стовпець у виборі, який ви можете сортувати за.

Таким чином ви чітко описуєте впорядковані впорядкування в базі даних, де воно має бути.


Це здається правильним способом зробити це. Однак я хотів би створити цей стіл замовлень на льоту. Я запропонував використовувати постійну таблицю в одній з відповідей. Це буде виграшним, коли я маю справу зі сотнями чи тисячами коментарів?
Лускунчик

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,')

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)) як у. але безрезультатно :-D ваша відповідь могла б дати мені підказку, якби я її уважно прочитав :-)
Майкл Буен

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;

[EDIT]

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] - головна таблиця, у якій є поле під назвою id, і, ids - це масив, який зберігає comments.id.

передано у postgresql 9.6


ви перевірили цей запит?
lalithkumar

тут, пам’ятайте, id - це тип масиву, наприклад, {1,2,3,4}.
користувач6161156

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)

Хитрість полягає в тому, щоб дати кожному значенню статусу interger і впорядкувати список числовим:

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


-1

Я погоджуюся з усіма іншими плакатами, які говорять "не роби цього" або "SQL не в цьому". Якщо ви хочете сортувати за деякими факторами коментарів, тоді додайте інший цілий стовпець до однієї зі своїх таблиць, щоб утримувати свої критерії сортування та сортувати за цим значенням. наприклад, "ЗАМОВИТИ BY comments.sort DESC" Якщо ви хочете кожен раз сортувати їх в іншому порядку ... SQL у цьому випадку не буде для вас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.