ZAMÓW WEDŁUG listy wartości IN

165

Mam proste zapytanie SQL w PostgreSQL 8.3, które przechwytuje kilka komentarzy. Podaję posortowaną listę wartości do INkonstrukcji w WHEREklauzuli:

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

To zwraca komentarze w dowolnej kolejności, która w moim przypadku jest podobna do identyfikatorów 1,2,3,4.

Chcę wynikające wierszy posortowanych jak listy w INkonstrukcie: (1,3,2,4).
Jak to osiągnąć?

orzechówka
źródło
I wolałbym nie tworzyć nowej tabeli tylko do sortowania (pomimo czystości SQL).
dziadek do orzechów
2
Mam teraz kilka odpowiedzi. Czy mogę uzyskać głos i komentarze, żeby wiedzieć, kto wygrał! Dzięki wszystkim :-)
dziadek do orzechów

Odpowiedzi:

106

Możesz to zrobić dość łatwo za pomocą (wprowadzonych w PostgreSQL 8.2) VALUES (), ().

Składnia będzie następująca:

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

źródło
2
@ user80168 A jeśli w klauzuli IN znajdują się tysiące wartości? bo muszę to zrobić dla tysięcy płyt
kamal
@kamal Do tego użyłem with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon
66

Tylko dlatego, że jest tak trudny do znalezienia i trzeba go rozpowszechniać: w mySQL można to zrobić znacznie prościej , ale nie wiem, czy działa to w innym SQL.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
das oe
źródło
3
Listę wartości należy podać dwukrotnie , na dwa różne sposoby. Nie takie proste. Przyjęta odpowiedź wymaga tego tylko raz (nawet jeśli w bardziej szczegółowy sposób). Jest to jeszcze prostsze w przypadku nowoczesnych Postgres (co pokazują nowsze odpowiedzi). Poza tym to pytanie wydaje się w końcu dotyczyć Postgresa.
Erwin Brandstetter
8
ERROR: cannot pass more than 100 arguments to a function
brauliobo
54

W Postgres 9.4 lub nowszym jest to prawdopodobnie najprostsze i najszybsze :

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • Używając nowego WITH ORDINALITY, wspomnianego już @a_horse .

  • Nie potrzebujemy podzapytania, możemy użyć funkcji zwracającej set, jak tabeli.

  • Literał łańcuchowy do przekazania w tablicy zamiast konstruktora ARRAY może być łatwiejszy do zaimplementowania w przypadku niektórych klientów.

Szczegółowe wyjaśnienie:

Erwin Brandstetter
źródło
46

Myślę, że ten sposób jest lepszy:

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
vantrung -cuncon
źródło
1
Udało mi się to zrobić z wartościami związanymi, tj .: ... order by id=? desc, id=? desc, id=? desci wydaje się, że działa dobrze :-)
KajMagnus
Działa w postgresach i wydaje się być najlepszym rozwiązaniem!
Mike Szyndel
To rozwiązanie załatwiło sprawę, ale: Czy ktoś zbadał, jak to rozwiązanie działa pod względem wydajności? Dodaje wielokrotne porządkowanie według klauzul. Dlatego może (jeszcze tego nie testowałem) spowolnić wykładniczo wraz ze wzrostem liczby identyfikatorów zamówienia? Wszelkie informacje na ten temat będą bardzo mile widziane!
Fabian Schöner
1
BŁĄD: listy docelowe mogą zawierać maksymalnie 1664 wpisy -> przy próbie uruchomienia długiego zapytania ...
Fatkhan Fauzi
@Manngo MS SQL. Nie pamiętam, która wersja.
Mogło
42

Z Postgres 9.4 można to zrobić trochę krócej:

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;

Lub nieco bardziej zwarty bez tabeli pochodnej:

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

Eliminacja konieczności ręcznego przypisywania / utrzymywania pozycji dla każdej wartości.

W Postgres 9.6 można to zrobić za pomocą 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 jest używane, więc lista wartości musi być określona tylko raz. Jeśli to nie jest ważne, można to również zapisać jako:

select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);
koń bez imienia
źródło
To nie powtarza ponownie całej INlisty z WHEREklauzuli w ORDER BYklauzuli, co sprawia, że ​​jest to najlepsza odpowiedź imho ... Teraz tylko po to, aby znaleźć coś podobnego dla MySQL ...
Stijn de Witt
1
Moja ulubiona odpowiedź, ale zwróć uwagę, że array_position nie działa z bigintem i musiałbyś rzucać: order by array_position(array[42,48,43], c.id::int);co może w niektórych przypadkach prowadzić do błędów.
aaandre
1
@aaandre Poniższe rzutowanie działa dobrze (przynajmniej w Postgres 12) array_position(array[42, 48, 43]::bigint[], c.id::bigint), więc nie ma potrzeby skracania bigintdo int.
Vic
29

Innym sposobem na zrobienie tego w Postgres byłoby użycie idxfunkcji.

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

Nie zapomnij idxnajpierw utworzyć funkcji, jak opisano tutaj: http://wiki.postgresql.org/wiki/Array_Index

Carl Mercier
źródło
11
Ta funkcja jest teraz dostępna w rozszerzeniu dołączonym do PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Zainstaluj za pomocą CREATE EXTENSION intarray;.
Alex Kahn
1
Po prostu piętrzą się dalej, dla użytkowników Amazon RDS funkcja migracji ROR enable_extensionpozwoli ci to aktywować, o ile użytkownik aplikacji jest członkiem rds_superusergrupy.
Dave S.
w PG 9.6.2 PG :: UndefinedFunction: ERROR: funkcja idx (integer [], integer) nie istnieje
Yakob Ubaidi
Dziękuję, najlepsza odpowiedź w połączeniu z komentarzem @ AlexKahna
Andrew
21

W Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
Clodoaldo Neto
źródło
2
Hum ... to wkurza jeśli position(id::text in '123,345,3,678'). Identyfikator 3będzie pasował przed identyfikatorem 345, prawda?
alanjds
4
Myślę, że masz rację i musiałbyś wtedy mieć zarówno ogranicznik początku, jak i końca, może na przykład: order by position (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush
3

Poszukując tego trochę więcej, znalazłem takie rozwiązanie:

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

Jednak wydaje się to raczej rozwlekłe i może powodować problemy z wydajnością w przypadku dużych zbiorów danych. Czy ktoś może wypowiedzieć się na temat tych kwestii?

orzechówka
źródło
7
Jasne, mogę je skomentować. Są rzeczy, w których SQL jest dobry i w których nie jest dobry. SQL nie jest w tym dobry. Po prostu posortuj wyniki w jakimkolwiek języku, w którym tworzysz zapytania; zaoszczędzi ci to wielu płaczu i zgrzytania zębami. SQL jest językiem zorientowanym na zestawy, a zbiory nie są kolekcjami uporządkowanymi.
kquinn
Hmmm ... Czy to jest oparte na osobistych doświadczeniach i testach? Z mojego doświadczenia wynika, że ​​jest to dość skuteczna technika składania zamówień. (Jednak przyjęta odpowiedź jest ogólnie lepsza, ponieważ eliminuje klauzulę „IN (…)”). Pamiętaj, że dla każdego rozsądnego rozmiaru zestawu wyników, wyprowadzenie zestawu powinno być kosztowną częścią. Gdy liczba rekordów spadnie do kilkuset lub mniej, sortowanie jest banalne.
dkretz
A jeśli INklauzula zawiera tysiące wartości ? ponieważ muszę to zrobić dla tysięcy płyt.
kamal
2

Aby to zrobić, myślę, że prawdopodobnie powinieneś mieć dodatkową tabelę „ZAMÓWIENIE”, która definiuje mapowanie identyfikatorów do zamówienia (skutecznie robiąc to, co mówi twoja odpowiedź na własne pytanie), której możesz następnie użyć jako dodatkowej kolumny w wybranym możesz następnie posortować.

W ten sposób wyraźnie określasz pożądaną kolejność w bazie danych, gdzie powinna się znajdować.

Paul Sonier
źródło
Wydaje się, że to właściwy sposób. Chciałbym jednak stworzyć tę tabelę zamówień w locie. Zasugerowałem użycie stałej tabeli w jednej z odpowiedzi. Czy będzie to skuteczne, gdy mam do czynienia z setkami lub tysiącami komentarzy?
dziadek do orzechów
2

sans SEQUENCE, działa tylko na 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
Michael Buen
źródło
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

lub jeśli wolisz zło od dobra:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')
Hafthor
źródło
0

A oto inne rozwiązanie, które działa i wykorzystuje stałą tabelę ( 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

Ale znowu nie jestem pewien, czy to działa.

Mam teraz kilka odpowiedzi. Czy mogę uzyskać głos i komentarze, żeby wiedzieć, kto wygrał!

Dziękuje wszystkim :-)

orzechówka
źródło
1
Twoja odpowiedź jest prawie taka sama w przypadku depesz, po prostu usuń c.ID IN (1,3,2,4). w każdym razie jego jest lepsze, używa JOIN, w miarę możliwości używaj metody łączenia ANSI SQL, nie używaj tabeli z przecinkami. Powinienem był uważnie przeczytać twoją odpowiedź, mam trudności z wymyśleniem aliasu dwóch kolumn, najpierw wypróbowałem to: (wartości (1,1) as x (id, sort_order), (3,2), (2,3), (4,4)) jako y. ale bezskutecznie :-D Twoja odpowiedź mogłaby dać mi wskazówkę, jeśli ją uważnie przeczytałem :-)
Michael Buen
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;

[EDYTOWAĆ]

unnest nie jest jeszcze wbudowany w 8.3, ale możesz go stworzyć samodzielnie (piękno każdego *):

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;
$$;

ta funkcja może działać w każdym typie:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Michael Buen
źródło
Dzięki, Michael, ale wydaje się, że nie ma takiej funkcji dla mojego PSQL i nie mogę znaleźć o niej żadnej wzmianki w dokumentacji. Czy to tylko 8.4?
dziadek do orzechów
unnest nie jest jeszcze wbudowany w 8.3, ale możesz zaimplementować go samodzielnie. zobacz kod powyżej
Michael Buen
0

Nieznaczne ulepszenie w stosunku do wersji używającej sekwencji, jak myślę:

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;

źródło
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)

tutaj [bbs] jest główną tabelą zawierającą pole o nazwie ids, a ids to tablica przechowująca komentarze.id.

przekazano w postgresql 9.6

user6161156
źródło
czy przetestowałeś to zapytanie?
lalithkumar
tutaj pamiętaj, ids to typ tablicy, na przykład {1, 2, 3, 4}.
user6161156
0

Zobaczmy, co zostało już powiedziane. Na przykład masz stół z kilkoma zadaniami:

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

I chcesz uporządkować listę zadań według statusu. Status to lista wartości ciągów:

(processing, pending,  completed, deleted)

Sztuczka polega na tym, aby nadać każdej wartości statusu interger i uporządkować listę numeryczną:

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;

Który prowadzi do:

 id |   status   |   description    
----+------------+------------------
  4 | processing | work on postgres
  3 | pending    | garden party
  5 | completed  | work on html
  6 | deleted    | need some rest

Kredyt @ user80168

Manuel
źródło
-1

Zgadzam się ze wszystkimi innymi plakatami, które mówią „nie rób tego” lub „SQL nie jest w tym dobry”. Jeśli chcesz sortować według jakiegoś aspektu komentarzy, dodaj kolejną kolumnę liczb całkowitych do jednej z tabel, aby przechowywać kryteria sortowania i sortować według tej wartości. np. "ORDER BY comments.sort DESC" Jeśli chcesz sortować je w innej kolejności za każdym razem, ... SQL w tym przypadku nie będzie dla Ciebie.

Trey
źródło