Menghitung DISTINCT melalui beberapa kolom

213

Apakah ada cara yang lebih baik untuk melakukan kueri seperti ini:

SELECT COUNT(*) 
FROM (SELECT DISTINCT DocumentId, DocumentSessionId
      FROM DocumentOutputItems) AS internalQuery

Saya perlu menghitung jumlah item berbeda dari tabel ini tetapi perbedaannya lebih dari dua kolom.

Permintaan saya berfungsi dengan baik tetapi saya bertanya-tanya apakah saya bisa mendapatkan hasil akhir hanya dengan menggunakan satu permintaan (tanpa menggunakan sub-permintaan)

Novitzky
sumber
IordanTanev, Mark Brackett, RC - terima kasih atas balasannya, ini merupakan percobaan yang bagus, tetapi Anda perlu memeriksa apa yang Anda lakukan sebelum memposting ke SO. Kueri yang Anda berikan tidak setara dengan kueri saya. Anda dapat dengan mudah melihat saya selalu memiliki hasil skalar tetapi permintaan Anda mengembalikan beberapa baris.
Novitzky
Baru saja memperbarui pertanyaan untuk memasukkan komentar klarifikasi Anda dari salah satu jawaban
Jeff
Ini pertanyaan yang bagus. Saya juga bertanya-tanya apakah ada cara yang lebih sederhana untuk melakukan ini
Anupam

Jawaban:

73

Jika Anda mencoba untuk meningkatkan kinerja, Anda bisa mencoba membuat kolom yang dihitung tetap pada nilai hash atau gabungan dari kedua kolom.

Setelah dipertahankan, asalkan kolom bersifat deterministik dan Anda menggunakan pengaturan basis data "waras", itu dapat diindeks dan / atau statistik dapat dibuat di atasnya.

Saya yakin jumlah berbeda dari kolom yang dihitung akan setara dengan kueri Anda.

Jason Horner
sumber
4
Saran yang sangat baik! Semakin banyak saya membaca, semakin saya menyadari bahwa SQL kurang tentang mengetahui sintaks dan fungsi dan lebih banyak tentang menerapkan logika murni .. Saya berharap saya punya 2 upvotes!
tumchaaditya
Saran yang terlalu bagus. Itu menghindari saya untuk menulis kode yang tidak perlu untuk ini.
Avrajit Roy
1
Tolong tambahkan contoh atau contoh kode untuk menunjukkan lebih banyak tentang apa artinya ini dan bagaimana melakukannya?
jayqui
52

Sunting: Diubah dari permintaan checksum-only yang kurang dapat diandalkan, saya telah menemukan cara untuk melakukan ini (dalam SQL Server 2005) yang bekerja cukup baik untuk saya dan saya dapat menggunakan kolom sebanyak yang saya butuhkan (dengan menambahkannya ke fungsi CHECKSUM ()). Fungsi REVERSE () mengubah int menjadi varchars untuk membuat perbedaan lebih dapat diandalkan

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems
JayTee
sumber
1
+1 Bagus, berfungsi sempurna (saat Anda memiliki jenis kolom yang tepat untuk melakukan CheckSum pada ...;)
Bernoulli IT
8
Dengan hash seperti Checksum (), ada kemungkinan kecil bahwa hash yang sama akan dikembalikan untuk input yang berbeda sehingga penghitungan mungkin sangat sedikit mati. HashBytes () adalah peluang yang lebih kecil tetapi masih belum nol. Jika kedua Id itu int (32b) maka "hash lossless" bisa menggabungkan mereka menjadi bigint (64b) seperti Id1 << 32 + Id2.
crokusek
1
kesempatannya tidak begitu kecil bahkan, terutama ketika Anda mulai menggabungkan kolom (yang memang dimaksudkan untuk itu). Saya ingin tahu tentang pendekatan ini dan dalam kasus tertentu checksum berakhir dengan hitungan 10% lebih kecil. Jika Anda memikirkannya sedikit lebih lama, Checksum hanya mengembalikan int, jadi jika Anda akan memeriksa rentang bigint penuh Anda akan berakhir dengan hitungan yang berbeda sekitar 2 miliar kali lebih kecil daripada yang sebenarnya. -1
pvolders
Memperbarui kueri untuk menyertakan penggunaan "REVERSE" untuk menghapus kemungkinan duplikat
JayTee
4
Bisakah kita menghindari CHECKSUM - bisakah kita menggabungkan kedua nilai bersama? Saya kira risiko itu mempertimbangkan hal yang sama: ('dia', 'seni') == 'dengar', 't'). Tapi saya pikir itu bisa diselesaikan dengan pembatas ketika @APC mengusulkan (beberapa nilai yang tidak muncul di kedua kolom), jadi 'he | ​​art'! = 'Dengar | t' Apakah ada masalah lain dengan "penggabungan" sederhana pendekatan?
Kacang Merah
31

Ada apa dengan kueri Anda saat ini yang tidak Anda sukai? Jika Anda khawatir bahwa DISTINCTdi dua kolom tidak hanya mengembalikan permutasi unik mengapa tidak mencobanya?

Ini tentu berfungsi seperti yang Anda harapkan di Oracle.

SQL> select distinct deptno, job from emp
  2  order by deptno, job
  3  /

    DEPTNO JOB
---------- ---------
        10 CLERK
        10 MANAGER
        10 PRESIDENT
        20 ANALYST
        20 CLERK
        20 MANAGER
        30 CLERK
        30 MANAGER
        30 SALESMAN

9 rows selected.


SQL> select count(*) from (
  2  select distinct deptno, job from emp
  3  )
  4  /

  COUNT(*)
----------
         9

SQL>

sunting

Saya pergi ke jalan buntu dengan analitik tetapi jawabannya sangat jelas ...

SQL> select count(distinct concat(deptno,job)) from emp
  2  /

COUNT(DISTINCTCONCAT(DEPTNO,JOB))
---------------------------------
                                9

SQL>

edit 2

Mengingat data berikut, solusi gabungan yang disediakan di atas akan salah hitung:

col1  col2
----  ----
A     AA
AA    A

Jadi kita memasukkan pemisah ...

select col1 + '*' + col2 from t23
/

Jelas pemisah yang dipilih harus berupa karakter, atau serangkaian karakter, yang tidak akan pernah muncul di kolom mana pun.

APC
sumber
+1 dari saya. Terima kasih atas jawaban anda. Permintaan saya berfungsi dengan baik tetapi saya bertanya-tanya apakah saya bisa mendapatkan hasil akhir hanya dengan menggunakan satu permintaan (tanpa menggunakan subquery)
Novitzky
20

Untuk menjalankan sebagai kueri tunggal, gabungkan kolom, lalu dapatkan jumlah instance instance string yang digabungkan.

SELECT count(DISTINCT concat(DocumentId, DocumentSessionId)) FROM DocumentOutputItems;

Di MySQL Anda dapat melakukan hal yang sama tanpa langkah penyatuan sebagai berikut:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Fitur ini disebutkan dalam dokumentasi MySQL:

http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count-distinct

spelunk1
sumber
Ini adalah pertanyaan SQL Server, dan kedua opsi yang Anda posting telah disebutkan dalam jawaban berikut untuk pertanyaan ini: stackoverflow.com/a/1471444/4955425 dan stackoverflow.com/a/1471713/4955425 .
sstan
1
FWIW, ini hampir berfungsi di PostgreSQL; hanya perlu kurung tambahan:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph
14

Bagaimana dengan sesuatu seperti:

pilih hitungan (*)
dari
  (pilih count (*) cnt
   dari DocumentOutputItems
   grup oleh DocumentId, DocumentSessionId) t1

Mungkin hanya melakukan hal yang sama seperti yang sudah Anda lakukan tetapi itu menghindari yang berbeda.

Trevor Tippins
sumber
dalam pengujian saya (menggunakan SET SHOWPLAN_ALL ON), ia memiliki rencana eksekusi yang sama dan persis TotalSubtreeCost sama
KM.
1
Bergantung pada kerumitan kueri asli, menyelesaikannya dengan ini GROUP BYdapat memperkenalkan beberapa tantangan tambahan pada transformasi kueri untuk mencapai output yang diinginkan (mis. Ketika kueri asli sudah memiliki GROUP BYatau HAVINGklausa ...)
Lukas Eder
8

Ini versi yang lebih pendek tanpa subselect:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Ini berfungsi dengan baik di MySQL, dan saya pikir pengoptimal memiliki waktu lebih mudah memahami yang ini.

Sunting: Rupanya saya salah membaca MSSQL dan MySQL - maaf soal itu, tapi mungkin bisa membantu.

Alexander Kjäll
sumber
6
di SQL Server Anda mendapatkan: Msg 102, Level 15, Negara 1, Baris 1 Sintaks salah dekat ','.
KM.
Inilah yang saya pikirkan. Saya ingin melakukan hal serupa di MSSQL jika memungkinkan.
Novitzky
@Kamil Nowicki, dalam SQL Server, Anda hanya dapat memiliki satu bidang dalam COUNT (), dalam jawaban saya, saya menunjukkan bahwa Anda dapat menggabungkan dua bidang menjadi satu dan mencoba pendekatan ini. Namun, saya hanya tetap dengan yang asli karena rencana kueri akan berakhir sama.
KM.
1
Tolong beri jawaban @JayTee. Itu bekerja seperti pesona.count ( distinct CHECKSUM ([Field1], [Field2])
Custodio
5

Banyak (sebagian besar?) Basis data SQL dapat bekerja dengan tuple seperti nilai sehingga Anda bisa melakukannya: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Jika basis data Anda tidak mendukung ini, dapat disimulasikan sesuai dengan saran @SELUM-Umut-turer dari CHECKSUM atau fungsi skalar lainnya yang memberikan keunikan yang baik mis COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Penggunaan terkait tuple sedang dilakukan IN kueri seperti: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

karmakaze
sumber
database apa yang didukung select count(distinct(a, b))? : D
Vytenis Bivainis
@VytenisBivainis saya tahu PostgreSQL tidak - tidak yakin sejak versi mana.
karmakaze
3

Tidak ada yang salah dengan permintaan Anda, tetapi Anda juga bisa melakukannya dengan cara ini:

WITH internalQuery (Amount)
AS
(
    SELECT (0)
      FROM DocumentOutputItems
  GROUP BY DocumentId, DocumentSessionId
)
SELECT COUNT(*) AS NumberOfDistinctRows
  FROM internalQuery
Bliek
sumber
3

Semoga ini berfungsi saya menulis di prima vista

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId
IordanTanev
sumber
7
Agar ini memberikan jawaban akhir, Anda harus membungkusnya dengan SELECT COUNT lain (*) FROM (...). Pada dasarnya jawaban ini hanya memberi Anda cara lain untuk membuat daftar nilai berbeda yang ingin Anda hitung. Ini tidak lebih baik dari solusi asli Anda.
Dave Costa
Terima kasih Dave. Saya tahu Anda dapat menggunakan grup dengan bukannya berbeda dalam kasus saya. Saya ingin tahu apakah Anda mendapatkan hasil akhir hanya dengan menggunakan satu permintaan. Saya pikir tidak mungkin tetapi saya mungkin salah.
Novitzky
3

Saya telah menggunakan pendekatan ini dan itu berhasil bagi saya.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Untuk kasus saya, ini memberikan hasil yang benar.

Jaanis Veinberg
sumber
Itu tidak memberi Anda hitungan nilai yang berbeda dalam hubungannya dengan dua kolom. Paling tidak di MySQL 5.8.
Anwar Shaikh
Pertanyaan ini ditandai dengan SQL Server, dan ini bukan sintaks SQL Server
Tab Alleman
2

jika Anda hanya memiliki satu bidang untuk "DISTINCT", Anda dapat menggunakan:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

dan itu mengembalikan rencana permintaan yang sama seperti aslinya, seperti diuji dengan SET SHOWPLAN_ALL ON. Namun Anda menggunakan dua bidang sehingga Anda dapat mencoba sesuatu yang gila seperti:

    SELECT COUNT(DISTINCT convert(varchar(15),DocumentId)+'|~|'+convert(varchar(15), DocumentSessionId)) 
    FROM DocumentOutputItems

tetapi Anda akan memiliki masalah jika NULL terlibat. Saya hanya akan tetap dengan permintaan asli.

KM.
sumber
+1 dari saya. Terima kasih tetapi saya akan tetap dengan permintaan saya seperti yang Anda sarankan. Menggunakan "konversi" dapat menurunkan kinerja lebih banyak.
Novitzky
2

Saya menemukan ini ketika saya mencari di Google untuk masalah saya sendiri, menemukan bahwa jika Anda menghitung objek yang berbeda, Anda mendapatkan nomor yang benar kembali (saya menggunakan MySQL)

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems
pembuat teh
sumber
5
Kueri di atas akan mengembalikan kumpulan hasil yang berbeda dari yang dicari OP ( kombinasi berbeda dari DocumentIddan DocumentSessionId). Alexander Kjäll sudah memposting jawaban yang benar jika OP menggunakan MySQL dan bukan MS SQL Server.
Anthony Geoghegan
1

Saya berharap MS SQL juga dapat melakukan sesuatu seperti COUNT (PERPINDAHAN A, B). Tetapi tidak bisa.

Awalnya jawaban JayTee sepertinya solusi bagi saya bu setelah beberapa tes CHECKSUM () gagal menciptakan nilai-nilai unik. Contoh singkatnya adalah, CHECKSUM (31.467.519) dan CHECKSUM (69.1111.823) memberikan jawaban yang sama yaitu 55.

Kemudian saya melakukan riset dan menemukan bahwa Microsoft TIDAK merekomendasikan menggunakan CHECKSUM untuk tujuan deteksi perubahan. Di beberapa forum beberapa disarankan menggunakan

SELECT COUNT(DISTINCT CHECKSUM(value1, value2, ..., valueN) + CHECKSUM(valueN, value(N-1), ..., value1))

tapi ini juga tidak nyaman.

Anda dapat menggunakan fungsi HASHBYTES () seperti yang disarankan dalam teka-teki TSQL CHECKSUM . Namun ini juga memiliki peluang kecil untuk tidak memberikan hasil yang unik.

Saya sarankan menggunakan

SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems
Oncel Umut TURER
sumber
1

Bagaimana dengan ini,

Select DocumentId, DocumentSessionId, count(*) as c 
from DocumentOutputItems 
group by DocumentId, DocumentSessionId;

Ini akan membuat kita menghitung semua kombinasi yang mungkin dari DocumentId, dan DocumentSessionId

Nikhil Singh
sumber
0

Ini bekerja untuk saya. Di oracle:

SELECT SUM(DECODE(COUNT(*),1,1,1))
FROM DocumentOutputItems GROUP BY DocumentId, DocumentSessionId;

Dalam jpql:

SELECT SUM(CASE WHEN COUNT(i)=1 THEN 1 ELSE 1 END)
FROM DocumentOutputItems i GROUP BY i.DocumentId, i.DocumentSessionId;
Nata
sumber
0

Saya memiliki pertanyaan serupa tetapi pertanyaan saya adalah sub-permintaan dengan data perbandingan dalam permintaan utama. sesuatu seperti:

Select code, id, title, name 
(select count(distinct col1) from mytable where code = a.code and length(title) >0)
from mytable a
group by code, id, title, name
--needs distinct over col2 as well as col1

mengabaikan kompleksitas ini, saya menyadari saya tidak bisa mendapatkan nilai a.code ke dalam subquery dengan permintaan sub ganda yang dijelaskan dalam pertanyaan asli

Select count(1) from (select distinct col1, col2 from mytable where code = a.code...)
--this doesn't work because the sub-query doesn't know what "a" is

Jadi akhirnya saya tahu saya bisa menipu, dan menggabungkan kolom:

Select count(distinct(col1 || col2)) from mytable where code = a.code...

Inilah yang akhirnya berhasil

Mark Rogers
sumber
0

Jika Anda bekerja dengan tipe data panjang tetap, Anda dapat binarymelakukan hal ini dengan sangat mudah dan sangat cepat. Dengan asumsi DocumentIddan DocumentSessionIdkeduanya ints, dan karena itu panjangnya 4 byte ...

SELECT COUNT(DISTINCT CAST(DocumentId as binary(4)) + CAST(DocumentSessionId as binary(4)))
FROM DocumentOutputItems

Masalah khusus saya mengharuskan saya untuk membagi SUMdengan COUNTkombinasi yang berbeda dari berbagai kunci asing dan bidang tanggal, pengelompokan dengan kunci asing lain dan kadang-kadang penyaringan dengan nilai atau kunci tertentu. Tabelnya sangat besar, dan menggunakan sub-kueri secara dramatis meningkatkan waktu kueri. Dan karena kerumitannya, statistik bukanlah pilihan yang layak. The CHECKSUMsolusi adalah juga terlalu lambat dalam konversi, terutama sebagai akibat dari berbagai jenis data, dan saya tidak bisa mengambil risiko tidak dapat diandalkan.

Namun, menggunakan solusi di atas hampir tidak ada peningkatan pada waktu permintaan (dibandingkan dengan hanya menggunakan SUM), dan harus sepenuhnya dapat diandalkan! Seharusnya bisa membantu orang lain dalam situasi yang sama jadi saya mempostingnya di sini.

IphStich
sumber
-1

Anda bisa menggunakan Count Function Twice.

Dalam hal ini, itu akan menjadi:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems
Bibek
sumber
ini tidak melakukan seperti yang
diminta
-1

Kode ini menggunakan perbedaan pada 2 parameter dan memberikan jumlah jumlah baris yang spesifik untuk jumlah baris yang berbeda tersebut. Ini bekerja untuk saya di MySQL seperti pesona.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
rishi jain
sumber