ORDER BY die IN-Werteliste

165

Ich habe eine einfache SQL-Abfrage in PostgreSQL 8.3, die eine Reihe von Kommentaren enthält. Ich stelle dem Konstrukt in der Klausel eine sortierte Liste von Werten zur Verfügung :INWHERE

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

Dies gibt Kommentare in einer beliebigen Reihenfolge zurück, die meiner Meinung nach IDs sind 1,2,3,4.

Ich möchte, dass die resultierenden Zeilen wie die Liste im INKonstrukt sortiert werden : (1,3,2,4).
Wie erreicht man das?

Nussknacker
quelle
Und ich würde es vorziehen, keine neue Tabelle nur für die Sortierung zu erstellen (trotz der SQL-Reinheit).
Nussknacker
2
Ich habe jetzt eine Reihe von Antworten. Kann ich ein paar Stimmen und Kommentare bekommen, damit ich weiß, wer der Gewinner ist? Vielen Dank an alle :-)
Nussknacker

Antworten:

106

Sie können dies ganz einfach mit (in PostgreSQL 8.2 eingeführt) VALUES (), () tun.

Die Syntax sieht folgendermaßen aus:

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

quelle
2
@ user80168 Was ist, wenn die IN-Klausel Tausende von Werten enthält? weil ich es für Tausende Rekorde tun muss
Kamal
@kamal Dafür habe ich verwendet with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon
66

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 anderem SQL funktioniert.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
das oe
quelle
3
Die Werteliste muss auf zwei verschiedene Arten zweimal bereitgestellt werden. Nicht so einfach. Die akzeptierte Antwort benötigt sie nur einmal (auch wenn sie ausführlicher ist). Und mit modernen Postgres ist es noch einfacher (wie in neueren Antworten gezeigt). Auch diese Frage scheint sich doch um Postgres zu handeln.
Erwin Brandstetter
8
ERROR: cannot pass more than 100 arguments to a function
Brauliobo
54

In Postgres 9.4 oder höher ist dies wahrscheinlich am einfachsten und schnellsten :

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, das @a_horse bereits erwähnt .

  • Wir brauchen keine Unterabfrage, wir können die Set-Return-Funktion wie eine Tabelle verwenden.

  • Ein String-Literal, das anstelle eines ARRAY-Konstruktors im Array übergeben werden kann, ist bei einigen Clients möglicherweise einfacher zu implementieren.

Ausführliche Erklärung:

Erwin Brandstetter
quelle
46

Ich finde diesen Weg 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
vantrung -cuncon
quelle
1
Ich konnte dies mit gebundenen Werten tun, dh: ... order by id=? desc, id=? desc, id=? descund es scheint gut zu funktionieren :-)
KajMagnus
Funktioniert in Postgres und scheint die beste Lösung zu sein!
Mike Szyndel
Diese Lösung hat den Trick für mich getan, aber: Hat jemand untersucht, wie diese Lösung in Bezug auf die Leistung funktioniert? Es wird eine mehrfache Reihenfolge nach Klauseln hinzugefügt. Daher kann es (ich habe es noch nicht getestet) mit zunehmender Anzahl von Auftrags-IDs exponentiell langsamer werden? Alle Informationen hierzu wäre sehr dankbar!
Fabian Schöner
1
FEHLER: Ziellisten können höchstens 1664 Einträge haben -> wenn Sie versuchen, eine lange Abfrage
auszuführen
@ Manngo MS SQL. Ich kann mich nicht erinnern, welche Version. Könnte 2012 gewesen sein.
Biko
42

Mit Postgres 9.4 geht das etwas kürzer:

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;

Oder etwas kompakter ohne abgeleitete Tabelle:

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

Die Notwendigkeit, jedem Wert manuell eine Position zuzuweisen / beizubehalten, entfällt.

Mit Postgres 9.6 kann dies folgendermaßen geschehen 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);

Der CTE wird verwendet, damit die Werteliste 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);
ein Pferd ohne Name
quelle
Dies wiederholt nicht die gesamte INListe aus der WHEREKlausel in der ORDER BYKlausel, was dies imho zur besten Antwort macht ... Jetzt nur, um etwas Ähnliches für MySQL zu finden ...
Stijn de Witt
1
Meine Lieblingsantwort, aber beachten Sie, dass array_position nicht mit bigint funktioniert und Sie Folgendes besetzen müssen: Dies order by array_position(array[42,48,43], c.id::int);kann in einigen Fällen zu Fehlern führen.
aaandre
1
@aaandre Die folgende Gießen arbeitet gut (in Postgres 12 zumindest) array_position(array[42, 48, 43]::bigint[], c.id::bigint), so dass keine Notwendigkeit trunkieren bigintzu int.
Vic
29

Eine andere Möglichkeit, dies in Postgres zu tun, wäre die Verwendung der idxFunktion.

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

Vergessen Sie nicht, zuerst die idxFunktion zu erstellen , wie hier beschrieben: http://wiki.postgresql.org/wiki/Array_Index

Carl Mercier
quelle
11
Diese Funktion ist jetzt in einer Erweiterung verfügbar, die mit PostgreSQL geliefert wird : postgresql.org/docs/9.2/static/intarray.html Installieren Sie sie mit CREATE EXTENSION intarray;.
Alex Kahn
1
Mit der ROR-Migrationsfunktion können enable_extensionSie diese Option für Amazon RDS-Benutzer aktivieren, solange Ihr App-Benutzer Mitglied der rds_superuserGruppe ist.
Dave S.
in PG 9.6.2 PG :: UndefinedFunction: ERROR: Funktion idx (Ganzzahl [], Ganzzahl) existiert nicht
Yakob Ubaidi
Vielen Dank, beste Antwort in Kombination mit @ AlexKahns Kommentar
Andrew
21

In Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
Clodoaldo Neto
quelle
2
Hum ... es nervt wenn position(id::text in '123,345,3,678'). Die ID 3wird vor der ID übereinstimmen 345, nicht wahr?
Alanjds
4
Ich denke, Sie haben Recht und müssten dann sowohl ein Start- als auch ein Endtrennzeichen haben, vielleicht wie folgt: Reihenfolge nach Position (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush
3

Als ich dies weiter recherchierte, 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 ziemlich ausführlich zu sein und kann zu Leistungsproblemen bei großen Datenmengen führen. Kann jemand zu diesen Themen Stellung nehmen?

Nussknacker
quelle
7
Klar, ich kann sie kommentieren. Es gibt Dinge, in denen SQL gut ist, und Dinge, in denen es nicht gut ist. SQL ist nicht gut darin. Sortieren Sie die Ergebnisse einfach in der Sprache, in der Sie die Abfragen durchführen. Es erspart Ihnen viel Wehklagen und Zähneknirschen. SQL ist eine satzorientierte Sprache, und Mengen sind keine geordneten Sammlungen.
Kquinn
Hmmm ... basiert das auf persönlichen Erfahrungen und Tests? Meine getestete Erfahrung ist, dass dies eine sehr effektive Technik für die Bestellung ist. (Die akzeptierte Antwort ist jedoch insgesamt besser, da die Klausel "IN (...)" entfällt.) Denken Sie daran, dass für jede vernünftige Größe der Ergebnismenge die Ableitung der Menge der teure Teil sein sollte. Sobald es auf mehrere hundert Datensätze oder weniger ankommt, ist das Sortieren trivial.
Dkretz
Was ist, wenn die INKlausel Tausende von Werten enthält ? weil ich es für tausende Platten machen muss.
Kamal
2

Um dies zu tun, sollten Sie wahrscheinlich eine zusätzliche "ORDER" -Tabelle haben, die die Zuordnung der zu bestellenden IDs definiert (effektiv das, was Ihre Antwort auf Ihre eigene Frage gesagt hat), die Sie dann als zusätzliche Spalte für Ihre 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.

Paul Sonier
quelle
Dies scheint der richtige Weg zu sein. Ich möchte diese Bestelltabelle jedoch im laufenden Betrieb erstellen. Ich habe vorgeschlagen, in einer der Antworten eine konstante Tabelle zu verwenden. Wird dies performant sein, wenn ich mit Hunderten oder Tausenden von Kommentaren zu tun habe?
Nussknacker
2

sans SEQUENCE, funktioniert nur unter 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
quelle
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

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,')
Hafthor
quelle
0

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

Aber ich bin mir auch nicht sicher, ob dies performant ist.

Ich habe jetzt eine Reihe von Antworten. Kann ich ein paar Stimmen und Kommentare bekommen, damit ich weiß, wer der Gewinner ist?

Danke an alle :-)

Nussknacker
quelle
1
Ihre Antwort ist mit depesz fast dieselbe. Entfernen Sie einfach die c.ID IN (1,3,2,4). Trotzdem ist es besser, er verwendet JOIN. Verwenden Sie so oft wie möglich die ANSI SQL-Verbindungsmethode. Verwenden Sie keine Tabellenkomma-Tabelle. Ich hätte Ihre Antwort sorgfältig lesen sollen. Es fällt mir schwer, herauszufinden, wie man die beiden Spalten aliasisiert. Zuerst habe ich Folgendes versucht: (Werte (1,1) als x (id, sort_order), (3,2), (2,3), (4,4)) als y. aber ohne Erfolg :-D Ihre Antwort hätte mir einen Hinweis geben können, wenn ich sie sorgfältig gelesen hätte :-)
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;

[BEARBEITEN]

unnest ist noch nicht in 8.3 integriert, aber Sie können selbst eines erstellen (die Schönheit eines jeden *):

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 funktionieren:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Michael Buen
quelle
Danke Michael, aber die unnest Funktion scheint für mein PSQL nicht zu existieren und ich kann sie auch in den Dokumenten nicht erwähnen. Ist es nur 8.4?
Nussknacker
unnest ist in 8.3 noch nicht integriert, aber Sie können es selbst implementieren. siehe den Code oben
Michael Buen
0

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;

quelle
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)

Hier ist [bbs] die Haupttabelle mit einem Feld namens ids, und ids ist das Array, in dem die Datei comment.id gespeichert ist.

bestanden in postgresql 9.6

user6161156
quelle
Hast du diese Abfrage getestet?
Lalithkumar
Denken Sie daran, ids ist ein Array-Typ wie {1,2,3,4}.
user6161156
0

Lassen Sie uns einen visuellen Eindruck von dem bekommen, 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 eine Interger zu geben 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

Credit @ user80168

Manuel
quelle
-1

Ich stimme allen anderen Postern zu, auf denen steht "Mach das nicht" oder "SQL ist nicht gut darin". Wenn Sie nach einer Facette von Kommentaren sortieren möchten, fügen Sie einer Ihrer Tabellen eine weitere Ganzzahlspalte hinzu, um Ihre Sortierkriterien zu speichern und nach diesem Wert zu sortieren. zB "ORDER BY comment.sort DESC" Wenn Sie diese jedes Mal in einer anderen Reihenfolge sortieren möchten, dann ... ist SQL in diesem Fall nichts für Sie.

Trey
quelle