Ich habe eine einfache SQL-Abfrage in PostgreSQL 8.3, die einige Kommentare enthält. Ich biete eine sortierte Liste von Werten an das IN
-Konstrukt in der WHERE
-Klausel
SELECT * FROM comments WHERE (comments.id IN (1,3,2,4));
Dies gibt Kommentare in einer willkürlichen Reihenfolge zurück, bei der es sich in meiner Regel um IDs wie 1,2,3,4
handelt.
Ich möchte, dass die resultierenden Zeilen wie die Liste im Konstrukt IN
sortiert werden: (1,3,2,4)
.
.__ Wie erreicht man das?
Mit (in PostgreSQL 8.2 eingeführten) VALUES (), () ist das problemlos möglich.
Die Syntax wird wie folgt aussehen:
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
Nur weil es so schwer zu finden ist und verbreitet werden muss: In mySQL kann dies viel einfacher gemacht werden , aber ich weiß nicht, ob es in anderen SQL-Versionen funktioniert.
SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
Ich finde dieser Weg ist besser:
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
In Postgres 9.4 oder höher ist dies wahrscheinlich einfachste und schnellste:
SELECT c.*
FROM comments c
JOIN unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER BY t.ord;
Mit dem neuen WITH ORDINALITY
wird das @a_horse bereits erwähnt .
Wir benötigen keine Unterabfrage, wir können die set-returning-Funktion wie eine Tabelle verwenden.
Ein String-Literal, das anstelle eines ARRAY-Konstruktors im Array übergeben wird, kann bei einigen Clients möglicherweise einfacher implementiert werden.
Ausführliche Erklärung:
Eine andere Möglichkeit, dies in Postgres zu tun, wäre die Verwendung der Funktion idx
.
SELECT *
FROM comments
ORDER BY idx(array[1,3,2,4], comments.id)
Vergessen Sie nicht, zuerst die idx
-Funktion zu erstellen, wie hier beschrieben: http://wiki.postgresql.org/wiki/Array_Index
Mit Postgres 9.4 kann dies etwas kürzer erfolgen:
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
Die Notwendigkeit, jedem Wert manuell eine Position zuzuweisen/beizubehalten, entfällt.
Mit Postgres 9.6 kann dies mit array_position()
erfolgen:
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);
Der CTE wird verwendet, damit die Liste der Werte nur einmal angegeben werden muss. Wenn das nicht wichtig ist, kann dies auch geschrieben werden als:
select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);
In Postgresql:
select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
Bei der Recherche fand ich diese Lösung:
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
Dies scheint jedoch recht ausführlich zu sein und kann zu Leistungsproblemen bei großen Datensätzen führen. Kann sich jemand zu diesen Problemen äußern?
sans SEQUENCE, funktioniert nur am 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
Ich denke, Sie sollten wahrscheinlich eine zusätzliche "ORDER" -Tabelle haben, in der die Zuordnung von IDs nach Reihenfolge definiert wird (was die Antwort auf Ihre eigene Frage bedeutet), die Sie als zusätzliche Spalte in Ihrer Auswahl verwenden können Sie können dann weiter sortieren.
Auf diese Weise beschreiben Sie explizit die gewünschte Reihenfolge in der Datenbank, wo sie sein sollte.
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
oder wenn Sie das Böse dem Guten vorziehen:
SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')
Verschaffen wir uns einen visuellen Eindruck von dem, was bereits gesagt wurde. Zum Beispiel haben Sie eine Tabelle mit einigen Aufgaben:
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
Und Sie möchten die Liste der Aufgaben nach ihrem Status sortieren. Der Status ist eine Liste von Zeichenfolgenwerten:
(processing, pending, completed, deleted)
Der Trick besteht darin, jedem Statuswert ein Interger zuzuweisen und die Liste numerisch zu ordnen:
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;
Was dazu führt:
id | status | description
----+------------+------------------
4 | processing | work on postgres
3 | pending | garden party
5 | completed | work on html
6 | deleted | need some rest
Gutschrift @ user80168
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)
hier ist [bbs] die Haupttabelle mit einem Feld namens ids und ids ist das Array, in dem die comments.id gespeichert ist.
bestanden in postgresql 9.6
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;
[BEARBEITEN]
unnest ist in 8.3 noch nicht integriert, aber Sie können selbst eine erstellen (die Schönheit eines beliebigen *):
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;
$$;
diese Funktion kann in jedem Typ arbeiten:
select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Und hier ist eine andere Lösung, die funktioniert und eine konstante Tabelle verwendet ( 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
Ich bin aber auch nicht sicher, ob dies performant ist.
Ich habe jetzt eine Menge Antworten. Kann ich ein paar Abstimmungen und Kommentare bekommen?
Vielen Dank an alle :-)
Eine leichte Verbesserung gegenüber der Version, die eine Sequenz verwendet, denke ich:
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;
Ich stimme mit allen anderen Plakaten überein, die sagen, "mach das nicht" oder "SQL ist nicht gut". Wenn Sie nach einer Facette von Kommentaren sortieren möchten, fügen Sie einer Ihrer Tabellen eine weitere Ganzzahlenspalte hinzu, um die Sortierkriterien zu speichern und nach diesem Wert zu sortieren. ZB "ORDER BY comments.sort DESC" Wenn Sie diese jedes Mal in einer anderen Reihenfolge sortieren möchten, dann ist ... SQL in diesem Fall nicht für Sie.