Bagaimana saya (atau bisakah saya) PILIH PERBEDAAN pada banyak kolom?

415

Saya perlu mengambil semua baris dari tabel di mana 2 kolom digabungkan semuanya berbeda. Jadi saya ingin semua penjualan yang tidak memiliki penjualan lain yang terjadi pada hari yang sama dengan harga yang sama. Penjualan yang unik berdasarkan hari dan harga akan diperbarui ke status aktif.

Jadi saya berpikir:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Tapi otakku sakit lebih jauh dari itu.

heats
sumber

Jawaban:

436
SELECT DISTINCT a,b,c FROM t

kira - kira setara dengan:

SELECT a,b,c FROM t GROUP BY a,b,c

Ide yang baik untuk membiasakan diri dengan sintaks GROUP BY, karena ini lebih kuat.

Untuk permintaan Anda, saya akan melakukannya seperti ini:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
Joel Coehoorn
sumber
117
Permintaan ini, walaupun benar dan diterima untuk tahun sekarang, sangat tidak efisien dan tidak perlu demikian. Jangan gunakan ini. Saya memberikan alternatif dan beberapa penjelasan dalam jawaban lain.
Erwin Brandstetter
1
Bukankah SELECT DISTINCT a, b, c DARI t persis hal yang sama dengan SELECT a, b, c FROM t GROUP OLEH a, b, c?
famargar
8
@amaramar untuk kasus sederhana, namun, tetapi mereka memiliki arti yang berbeda secara semantik, dan mereka berbeda dalam hal apa yang dapat Anda lakukan untuk langkah ketika membangun kueri yang lebih besar. Selain itu, orang-orang di forum teknologi sering kali sangat luar biasa dalam hal-hal, saya merasa sering berguna untuk menambahkan kata musang ke posting saya dalam konteks ini.
Joel Coehoorn
344

Jika Anda mengumpulkan jawaban sejauh ini, bersihkan dan tingkatkan, Anda akan sampai pada pertanyaan superior ini:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Yang jauh lebih cepat daripada keduanya. Nukes kinerja jawaban yang saat ini diterima oleh faktor 10 - 15 (dalam tes saya pada PostgreSQL 8.4 dan 9.1).

Namun ini masih jauh dari optimal. Gunakan NOT EXISTSsemi-join (anti-) untuk kinerja yang lebih baik. EXISTSadalah SQL standar, telah ada selamanya (setidaknya sejak PostgreSQL 7.2, jauh sebelum pertanyaan ini diajukan) dan sangat cocok dengan persyaratan yang disajikan:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> biola di sini
Old SQL Fiddle

Kunci unik untuk mengidentifikasi baris

Jika Anda tidak memiliki kunci utama atau unik untuk tabel ( iddalam contoh), Anda bisa mengganti dengan kolom sistem ctiduntuk tujuan permintaan ini (tetapi tidak untuk beberapa tujuan lain):

   AND    s1.ctid <> s.ctid

Setiap tabel harus memiliki kunci utama. Tambahkan satu jika Anda belum memilikinya. Saya menyarankan satu serialatau satu IDENTITYkolom di Postgres 10+.

Terkait:

Bagaimana ini lebih cepat?

Subquery di EXISTSanti-semi-join dapat berhenti mengevaluasi begitu dupe pertama ditemukan (tidak ada gunanya mencari lebih lanjut). Untuk tabel dasar dengan beberapa duplikat, ini hanya sedikit lebih efisien. Dengan banyak duplikat ini menjadi jauh lebih efisien.

Kecualikan pembaruan kosong

Untuk baris yang sudah memiliki status = 'ACTIVE'pembaruan ini tidak akan mengubah apa pun, tetapi tetap memasukkan versi baris baru dengan biaya penuh (pengecualian kecil berlaku). Biasanya, Anda tidak menginginkan ini. Tambahkan WHEREkondisi lain seperti yang ditunjukkan di atas untuk menghindari ini dan membuatnya lebih cepat:

Jika statusdidefinisikan NOT NULL, Anda dapat menyederhanakan untuk:

AND status <> 'ACTIVE';

Jenis data kolom harus mendukung <>operator. Beberapa tipe suka jsontidak. Lihat:

Perbedaan yang halus dalam penanganan NULL

Kueri ini (tidak seperti jawaban yang saat ini diterima oleh Joel ) tidak memperlakukan nilai NULL sebagai sama. Dua baris berikut untuk (saleprice, saledate)dikualifikasikan sebagai "berbeda" (meskipun terlihat identik dengan mata manusia):

(123, NULL)
(123, NULL)

Juga melewati dalam indeks unik dan hampir di tempat lain, karena nilai NULL tidak membandingkan sama dengan standar SQL. Lihat:

Otoh, GROUP BY, DISTINCTatau DISTINCT ON ()nilai-nilai memperlakukan NULL sebagai sama. Gunakan gaya permintaan yang sesuai tergantung pada apa yang ingin Anda capai. Anda masih dapat menggunakan kueri yang lebih cepat ini dengan IS NOT DISTINCT FROMalih - alih =untuk setiap atau semua perbandingan untuk membuat NULL membandingkannya. Lebih:

Jika semua kolom yang dibandingkan didefinisikan NOT NULL, tidak ada ruang untuk ketidaksepakatan.

Erwin Brandstetter
sumber
16
Jawaban yang bagus. Saya seorang sql server, jadi saran pertama untuk menggunakan tuple dengan cek IN () tidak muncul di benak saya. Saran yang tidak ada biasanya akan berakhir dengan rencana eksekusi yang sama di server sql sebagai gabungan batin.
Joel Coehoorn
2
Bagus. Penjelasannya sangat meningkatkan nilai jawaban. Saya hampir tergoda untuk menjalankan beberapa tes dengan Oracle untuk melihat bagaimana rencana membandingkan dengan Postgres dan SQLServer.
Peter
2
@alairock: Di mana Anda mendapatkan itu? Bagi Postgres, yang terjadi adalah sebaliknya . Sementara menghitung semua baris, count(*)adalah lebih efisien daripada count(<expression>). Cobalah. Postgres memiliki implementasi yang lebih cepat untuk varian fungsi agregat ini. Mungkin Anda membingungkan Postgres dengan RDBMS lain?
Erwin Brandstetter
6
@alairock: Saya kebetulan adalah co-penulis halaman itu dan tidak mengatakan hal semacam itu.
Erwin Brandstetter
2
@ ErwinBrandstetter, Anda selalu benar dengan jawaban Anda di tumpukan. Anda telah membantu selama bertahun-tahun dalam jumlah yang hampir tidak terbayangkan. Adapun contoh ini, saya tahu beberapa cara berbeda untuk menyelesaikan masalah saya, tetapi saya ingin melihat seseorang telah menguji efisiensi di antara berbagai kemungkinan. Terima kasih.
WebWanderer
24

Masalah dengan kueri Anda adalah bahwa ketika menggunakan klausa GROUP BY (yang pada dasarnya Anda lakukan dengan menggunakan berbeda), Anda hanya dapat menggunakan kolom yang dikelompokkan berdasarkan atau fungsi agregat. Anda tidak dapat menggunakan id kolom karena ada nilai yang berpotensi berbeda. Dalam kasus Anda selalu ada hanya satu nilai karena klausa HAVING, tetapi sebagian besar RDBMS tidak cukup pintar untuk mengenalinya.

Namun ini harus bekerja (dan tidak perlu bergabung):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

Anda juga bisa menggunakan MAX atau AVG alih-alih MIN, hanya penting untuk menggunakan fungsi yang mengembalikan nilai kolom jika hanya ada satu baris yang cocok.

Christian Berg
sumber
1

Saya ingin memilih nilai yang berbeda dari satu kolom 'GrondOfLucht' tetapi mereka harus diurutkan dalam urutan seperti yang diberikan dalam kolom 'sortering'. Saya tidak bisa mendapatkan nilai yang berbeda hanya menggunakan satu kolom

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Ini juga akan memberikan kolom 'sortering' dan karena 'GrondOfLucht' DAN 'sortering' tidak unik, hasilnya adalah SEMUA baris.

gunakan GROUP untuk memilih catatan 'GrondOfLucht' dalam urutan yang diberikan oleh 'sortering

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
fil eilering
sumber
Ini pada dasarnya menjelaskan apa yang dilakukan jawaban yang diterima, tetapi saya sarankan untuk tidak menggunakan nama-nama tersebut sebagai contoh (setidaknya menerjemahkannya). PS: Saya sarankan selalu menyebutkan semuanya dalam Bahasa Inggris di semua proyek bahkan jika Anda belanda.
Kerwin Sneijders
0

Jika DBMS Anda tidak mendukung perbedaan dengan beberapa kolom seperti ini:

select distinct(col1, col2) from table

Multi pilih secara umum dapat dijalankan dengan aman sebagai berikut:

select distinct * from (select col1, col2 from table ) as x

Karena ini dapat bekerja pada sebagian besar DBMS dan ini diharapkan lebih cepat daripada solusi kelompok karena Anda menghindari fungsi pengelompokan.

Abdulhafeth Sartawi
sumber