ORDER OLEH daftar nilai IN

166

Saya punya query SQL sederhana di PostgreSQL 8.3 yang mengambil banyak komentar. Saya memberikan daftar nilai yang diurutkan ke INkonstruk dalam WHEREklausa:

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

Ini mengembalikan komentar dalam urutan acak yang kebetulan seperti id 1,2,3,4.

Saya ingin baris yang dihasilkan diurutkan seperti daftar di INkonstruk: (1,3,2,4).
Bagaimana cara mencapainya?

alat pemecah buah keras
sumber
Dan saya lebih suka untuk tidak membuat tabel baru hanya untuk penyortiran (meskipun kemurnian SQL).
nutcracker
2
Saya punya banyak jawaban sekarang. Bisakah saya mendapatkan suara dan komentar, jadi saya tahu mana pemenangnya! Terima kasih semua :-)
nutcracker

Jawaban:

107

Anda dapat melakukannya dengan mudah dengan (diperkenalkan pada PostgreSQL 8.2) VALUES (), ().

Sintaksnya akan seperti ini:

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

sumber
2
@ user80168 Bagaimana jika ada ribuan nilai dalam klausa IN? karena saya harus melakukannya untuk ribuan catatan
kamal
@ kamal Untuk itu saya telah menggunakan with ordered_products as (select row_number() OVER (ORDER BY whatever) as reportingorder, id from comments) ... ORDER BY reportingorder.
Noumenon
66

Hanya karena sangat sulit untuk menemukan dan harus disebarkan: di mySQL ini bisa dilakukan lebih sederhana , tetapi saya tidak tahu apakah itu bekerja di SQL lain.

SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
das oe
sumber
3
Daftar nilai harus disediakan dua kali , dengan dua cara berbeda. Tidak sesederhana itu. Jawaban yang diterima hanya perlu satu kali (bahkan jika dengan cara yang lebih verbose). Dan bahkan lebih sederhana dengan Postgres modern (seperti yang ditunjukkan dalam jawaban yang lebih baru). Selain itu, pertanyaan ini sepertinya tentang Postgres.
Erwin Brandstetter
8
ERROR: cannot pass more than 100 arguments to a function
brauliobo
54

Dalam Postgres 9.4 atau lebih baru, ini mungkin paling sederhana dan tercepat :

SELECT c.*
FROM   comments c
JOIN   unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER  BY t.ord;
  • Menggunakan yang baru WITH ORDINALITY, yang @a_horse sudah sebutkan .

  • Kami tidak memerlukan subquery, kami dapat menggunakan fungsi set-return seperti tabel.

  • Sebuah string literal untuk diserahkan dalam array daripada konstruktor ARRAY mungkin lebih mudah untuk diterapkan dengan beberapa klien.

Penjelasan detail:

Erwin Brandstetter
sumber
46

Saya pikir cara ini lebih baik:

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
sumber
1
Saya bisa melakukan ini dengan nilai-nilai terikat, yaitu: ... order by id=? desc, id=? desc, id=? descdan tampaknya berfungsi dengan baik :-)
KajMagnus
Bekerja di postgres dan tampaknya menjadi solusi terbaik!
Mike Szyndel
Solusi ini melakukan trik untuk saya, tetapi: Adakah yang meneliti bagaimana solusi ini melakukan kinerja-bijaksana? Itu menambahkan perintah multple oleh klausa. Karena itu mungkin (saya belum mengujinya) menjadi lebih lambat secara eksponensial dengan meningkatnya jumlah pesanan-id? Setiap informasi tentang ini akan sangat dihargai!
Fabian Schöner
1
GALAT: daftar target dapat memiliki paling banyak 1664 entri -> ketika Anda mencoba menjalankan kueri panjang ...
Fatkhan Fauzi
@Manngo MS SQL. Tidak ingat versi mana. Mungkin sudah 2012.
biko
43

Dengan Postgres 9.4 ini dapat dilakukan sedikit lebih pendek:

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;

Atau sedikit lebih kompak tanpa tabel turunan:

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

Menghapus kebutuhan untuk secara manual menetapkan / mempertahankan posisi untuk setiap nilai.

Dengan Postgres 9.6 ini dapat dilakukan dengan menggunakan 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 digunakan sehingga daftar nilai hanya perlu ditentukan satu kali. Jika itu tidak penting, ini juga dapat ditulis sebagai:

select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);
seekor kuda tanpa nama
sumber
Ini tidak mengulangi seluruh INdaftar dari WHEREklausa lagi di ORDER BYklausa, yang menjadikan ini jawaban terbaik imho ... Sekarang hanya untuk menemukan sesuatu yang serupa untuk MySQL ...
Stijn de Witt
1
Jawaban favorit saya tetapi perhatikan bahwa array_position tidak berfungsi dengan bigint dan Anda harus melakukan cast: order by array_position(array[42,48,43], c.id::int);yang, dapat menyebabkan bug dalam beberapa kasus.
aaandre
1
@aaandre The casting berikut bekerja dengan baik (di Postgres 12 setidaknya) array_position(array[42, 48, 43]::bigint[], c.id::bigint), jadi tidak perlu truncate bigintuntuk int.
Vic
29

Cara lain untuk melakukannya di Postgres adalah dengan menggunakan idxfungsinya.

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

Jangan lupa untuk membuat idxfungsi terlebih dahulu, seperti dijelaskan di sini: http://wiki.postgresql.org/wiki/Array_Index

Carl Mercier
sumber
11
Fungsi ini sekarang tersedia dalam ekstensi yang datang dengan PostgreSQL: postgresql.org/docs/9.2/static/intarray.html Instal dengan CREATE EXTENSION intarray;.
Alex Kahn
1
Hanya menumpuk lebih lanjut, untuk pengguna Amazon RDS, fungsi migrasi ROR enable_extensionakan memungkinkan Anda mengaktifkannya selama pengguna aplikasi Anda adalah anggota rds_superusergrup.
Dave S.
dalam PG 9.6.2 PG :: UndefinedFunction: ERROR: function idx (integer [], integer) tidak ada
Yakob Ubaidi
Terima kasih, jawaban terbaik ketika dikombinasikan dengan komentar @ AlexKahn
Andrew
21

Dalam Postgresql:

select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
Clodoaldo Neto
sumber
2
Hum ... itu mengganggu jika position(id::text in '123,345,3,678'). Id 3akan cocok sebelum id 345, bukan?
alanjds
4
Saya pikir Anda benar dan perlu memiliki pembatas awal dan akhir kemudian, mungkin seperti: order by position (',' || id :: text || ',' in ', 1,3,2,4, ')
Michael Rush
3

Pada penelitian ini lagi saya menemukan solusi ini:

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

Namun ini tampaknya agak bertele-tele dan mungkin memiliki masalah kinerja dengan dataset besar. Adakah yang bisa berkomentar tentang masalah ini?

alat pemecah buah keras
sumber
7
Tentu, saya bisa mengomentari mereka. Ada beberapa hal yang bisa dilakukan oleh SQL, dan hal-hal yang tidak bisa dilakukan dengan baik. SQL tidak pandai dalam hal ini. Hanya mengurutkan hasil dalam bahasa apa pun dari mana Anda membuat kueri; itu akan menghemat banyak ratapan dan kertakan gigi. SQL adalah bahasa yang berorientasi pada set, dan set koleksi tidak diurutkan.
kquinn
Hmmm ... Apakah itu berdasarkan pengalaman pribadi dan pengujian? Pengalaman saya yang teruji adalah bahwa ini adalah teknik pemesanan yang cukup efektif. (Namun, jawaban yang diterima secara keseluruhan lebih baik karena menghilangkan klausa "IN (...)"). Ingat bahwa untuk setiap ukuran set hasil yang masuk akal, menurunkan set harus menjadi bagian yang mahal. Setelah beberapa ratus catatan atau kurang, pengurutan itu sepele.
dkretz
Bagaimana jika ada ribuan nilai dalam INklausa? karena saya harus melakukannya untuk ribuan catatan.
kamal
2

Untuk melakukan ini, saya pikir Anda mungkin harus memiliki tabel "ORDER" tambahan yang mendefinisikan pemetaan ID untuk dipesan (secara efektif melakukan apa yang dikatakan oleh jawaban Anda atas pertanyaan Anda sendiri), yang kemudian dapat Anda gunakan sebagai kolom tambahan pada pilih yang Anda kemudian dapat memilah.

Dengan cara itu, Anda secara eksplisit menggambarkan pemesanan yang Anda inginkan dalam database, di mana seharusnya.

Paul Sonier
sumber
Ini sepertinya cara yang tepat untuk melakukannya. Namun saya ingin membuat tabel pemesanan dengan cepat. Saya telah menyarankan menggunakan tabel konstan di salah satu jawaban. Apakah ini akan menjadi pemain ketika saya berurusan dengan ratusan atau ribuan komentar?
nutcracker
2

sans SEQUENCE, hanya berfungsi pada 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
sumber
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

atau jika Anda lebih memilih kejahatan daripada kebaikan:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')
Hafthor
sumber
0

Dan inilah solusi lain yang berfungsi dan menggunakan tabel konstan ( 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

Tapi sekali lagi saya tidak yakin ini adalah pemain.

Saya punya banyak jawaban sekarang. Bisakah saya mendapatkan suara dan komentar, jadi saya tahu mana pemenangnya!

Terima kasih semuanya :-)

alat pemecah buah keras
sumber
1
jawaban Anda hampir sama dengan depesz, cukup hapus c.ID IN (1,3,2,4). Lagi pula itu lebih baik, ia menggunakan BERGABUNG, sebisa mungkin gunakan cara bergabung ANSI SQL, jangan gunakan tabel koma tabel. saya harus membaca jawaban Anda dengan hati-hati, saya mengalami kesulitan mencari tahu cara alias dua kolom, pertama saya mencoba ini: (nilai (1,1) sebagai x (id, sort_order), (3,2), (2,3), (4,4)) sebagai y. tetapi tidak berhasil :-D jawaban Anda bisa memberikan saya petunjuk jika saya sudah membaca dengan hati-hati :-)
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;

[EDIT]

Unest belum terpasang di 8.3, tetapi Anda bisa membuatnya sendiri (keindahan dari * apa pun):

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

fungsi itu dapat bekerja dalam jenis apa pun:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Michael Buen
sumber
Terima kasih Michael, tetapi fungsi yang paling tidak masuk akal sepertinya tidak ada untuk PSQL saya dan saya tidak dapat menemukannya di dokumen juga. Apakah hanya 8,4?
nutcracker
undest belum terpasang di 8.3, tetapi Anda bisa menerapkannya sendiri. lihat kode di atas
Michael Buen
0

Peningkatan sedikit atas versi yang menggunakan urutan saya pikir:

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;

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

di sini, [bbs] adalah tabel utama yang memiliki bidang yang disebut id, dan, id adalah array yang menyimpan comments.id.

disahkan pada postgresql 9.6

pengguna6161156
sumber
apakah Anda menguji permintaan ini?
lalithkumar
di sini, ingat, id adalah tipe array, seperti, {1,2,3,4}.
user6161156
0

Mari kita mendapatkan kesan visual tentang apa yang sudah dikatakan. Misalnya Anda memiliki tabel dengan beberapa tugas:

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

Dan Anda ingin memesan daftar tugas berdasarkan statusnya. Statusnya adalah daftar nilai string:

(processing, pending,  completed, deleted)

Caranya adalah dengan memberikan setiap nilai status interger dan memesan daftar numerik:

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;

Yang mengarah ke:

 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
sumber
-1

Saya setuju dengan semua poster lain yang mengatakan "jangan lakukan itu" atau "SQL tidak bagus dalam hal itu". Jika Anda ingin mengurutkan berdasarkan segi komentar lalu tambahkan kolom integer lain ke salah satu tabel Anda untuk menampung kriteria pengurutan Anda dan urutkan berdasarkan nilai itu. mis. "ORDER BY comments.sort DESC" Jika Anda ingin mengurutkan ini dalam urutan yang berbeda setiap kali ... SQL tidak cocok untuk Anda dalam kasus ini.

Angka tiga
sumber