Birden çok sütun üzerinde DISTINCT sayma


213

Böyle bir sorgu yapmanın daha iyi bir yolu var mı:

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

Bu tablodan farklı öğe sayısını saymak gerekiyor ama farklı iki sütun üzerinde.

Benim sorgu iyi çalışıyor ama ben sadece bir sorgu kullanarak (bir alt sorgu kullanmadan) nihai sonuç alabilir miyim merak ediyordum


IordanTanev, Mark Brackett, RC - cevaplar için teşekkürler, güzel bir denemeydi, ancak SO'ya göndermeden önce ne yaptığınızı kontrol etmeniz gerekiyor. Sağladığınız sorgular sorgumla eşdeğer değil. Her zaman bir skaler sonucum olduğunu kolayca görebilirsiniz, ancak sorgunuz birden çok satır döndürür.
Novitzky

Cevaplardan birinden açıklayıcı yorumunuzu eklemek için soruyu güncelledik
Jeff


Bu iyi bir soru. Bunu yapmak için daha basit bir yol olsaydı ben de merak ediyordum
Anupam

Yanıtlar:


73

Performansı artırmaya çalışıyorsanız, iki sütunun karma veya bitmiş değeri üzerinde kalıcı bir hesaplanmış sütun oluşturmayı deneyebilirsiniz.

Devam edildikten sonra, sütun deterministik olduğu ve "aklı başında" veritabanı ayarlarını kullandığınız takdirde, dizine eklenebilir ve / veya üzerinde istatistik oluşturulabilir.

Hesaplanan sütunun ayrı bir sayısının sorgunuza eşdeğer olacağına inanıyorum.


4
Mükemmel öneri! Ne kadar çok okursam, SQL'in daha fazla sözdizimi ve fonksiyonları bilmekle ilgili olduğunu ve saf mantık uygulama hakkında daha fazla olduğunu anlıyorum .. Keşke 2 upvotes olsaydı!
tumchaaditya

Çok iyi bir öneri. Buna gereksiz kod yazmamdan kaçındı.
Avrajit Roy

1
Bunun ne anlama geldiği ve nasıl yapılacağı hakkında daha fazla bilgi için lütfen bir örnek veya kod örneği ekler misiniz?
jayqui

52

Düzenleme: Güvenilir az sağlama-yalnızca sorgudan değiştirildi benim için oldukça iyi çalışır ve (onları ekleyerek) (Ben bunları ekleyerek CHECKSUM () işlevi). REVERSE () işlevi, farklıları daha güvenilir hale getirmek için girişleri varcharlara dönüştürür

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems

1
+1 Güzel olan, mükemmel çalışıyor (bir CheckSum üzerinde doğru sütun türlerine sahip olduğunuzda ...;)
Bernoulli IT

8
Checksum () gibi hashlerle, farklı girdiler için aynı hashın döndürülme olasılığı azdır, bu nedenle sayım çok az olabilir. HashBytes () çok daha küçük bir şanstır, ancak yine de sıfır değildir. Eğer bu iki ID int (32b) olsaydı, "kayıpsız bir karma" onları Id1 << 32 + Id2 gibi bir bigint (64b) halinde birleştirebilirdi.
crokusek

1
şans, özellikle sütunları birleştirmeye başladığınızda bile çok küçük değildir (bunun için olması gerekiyordu). Bu yaklaşımı merak ettim ve belirli bir durumda sağlama toplamı% 10 daha küçük bir sayı ile sonuçlandı. Biraz daha uzun düşünürseniz, Checksum sadece bir int döndürür, bu nedenle tam bir bigint aralığını denetlerseniz, gerçekte olduğundan yaklaşık 2 milyar kat daha küçük bir sayı elde edersiniz. -1
pvolders

Sorgu, çoğaltma şansını ortadan kaldırmak için "TERS" kullanımını içerecek şekilde güncellendi
JayTee

4
CHECKSUM'dan kaçınabilir miyiz - iki değeri birleştirebilir miyiz? Sanırım bu riskleri aynı şey olarak düşünüyoruz: ('o', 'sanat') == 'duymak', 't'). Ancak @APC'nin önerdiği gibi bir sınırlayıcıyla çözülebileceğini düşünüyorum (her iki sütunda görünmeyen bir değer), bu yüzden 'he | ​​art'! = 'Hear | t' Basit bir "birleştirme" ile ilgili başka sorunlar var mı yaklaşmak?
Kızıl Bezelye

31

Mevcut sorgunuzda beğenmediğiniz şey nedir? Eğer DISTINCTiki sütun arasında sadece benzersiz permütasyonlar dönmüyor endişe varsa neden denemek?

Kesinlikle Oracle'da beklediğiniz gibi çalışır.

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>

Düzenle

Analitik ile kör bir yola indim ama cevap iç karartıcı bir şekilde belliydi ...

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

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

SQL>

düzenleme 2

Aşağıdaki veriler göz önüne alındığında, yukarıda sağlanan birleştirme çözümü yanlış sayılır:

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

Bir ayırıcı ekleyelim ...

select col1 + '*' + col2 from t23
/

Açıkçası, seçilen ayırıcı, hiçbir zaman iki sütunda görünmeyecek bir karakter veya karakter kümesi olmalıdır.


Benden +1. Cevabınız için teşekkürler. Benim sorgu iyi çalışıyor ama ben sadece bir sorgu kullanarak (bir alt sorgu kullanmadan) son sonucu alabilir miyim merak ediyordum
Novitzky

20

Tek bir sorgu olarak çalıştırmak için sütunları birleştirin, ardından birleştirilmiş dizenin farklı örnek sayısını alın.

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

MySQL'de aynı işlemi birleştirme adımı olmadan aşağıdaki gibi yapabilirsiniz:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Bu özellik MySQL belgelerinde belirtilmiştir:

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


Bu bir SQL Server sorusuydu ve her iki seçeneğiniz de bu sorunun aşağıdaki yanıtlarında zaten belirtilmişti: stackoverflow.com/a/1471444/4955425 ve stackoverflow.com/a/1471713/4955425 .
sstan

1
FWIW, bu neredeyse PostgreSQL'de çalışıyor; sadece ekstra parantez gerekir:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

Nasıl bir şey hakkında:

sayı seç (*)
itibaren
  (sayımı seçin (*) cnt
   DocumentOutputItems tarafından
   DocumentId, DocumentSessionId tarafından gruplandır) t1

Muhtemelen zaten seninle aynı şeyi yapar ama DISTINCT'den kaçınır.


Testlerimde (SET SHOWPLAN_ALL ON kullanarak), aynı yürütme planına ve aynı TotalSubtreeCost
.

1
Orijinal sorgunun karmaşıklığına bağlı olarak, bunu çözmek GROUP BY, istenen çıktıyı elde etmek için sorgu dönüşümüne birkaç ek zorluk getirebilir (örneğin, orijinal sorgunun zaten GROUP BYveya HAVINGyan tümceleri olduğunda ...)
Lukas Eder

8

Alt seçim olmadan daha kısa bir sürüm:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

MySQL'de iyi çalışıyor ve optimize edicinin bunu anlamak için daha kolay bir zaman olduğunu düşünüyorum.

Düzenleme: Görünüşe göre MSSQL ve MySQL yanlış - bunun için üzgünüm, ama belki yine de yardımcı olur.


6
SQL Server'da şunu alırsınız: Msg 102, Seviye 15, Durum 1, Satır 1 ',' yakınında yanlış sözdizimi.
KM.

Ben de öyle düşünüyordum. Mümkünse MSSQL'de benzer bir şey yapmak istiyorum.
Novitzky

@Kamil Nowicki, SQL Server'da, bir COUNT () içinde sadece bir alana sahip olabilirsiniz, cevabımda iki alanı bir araya getirebileceğinizi ve bu yaklaşımı deneyebileceğinizi göstereceğim. Ancak, sorgu planları aynı sona erecek çünkü ben sadece orijinal sopa.
KM.

1
Lütfen @JayTee cevabına bir göz atın. Mucizevi şekilde çalışır. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio

5

Birçok (en çok?) SQL veritabanı değer gibi tuples ile çalışabilir, böylece şunları yapabilirsiniz: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Veritabanınız bunu desteklemiyorsa, @ oncel-umut-turer'in CHECKSUM önerisine veya iyi bir benzersizlik sağlayan diğer skaler fonksiyonlarına göre simüle edilebilir örn COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Tuples ile ilgili bir kullanım aşağıdaki INgibi sorguları gerçekleştirmektir : SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));


hangi veritabanlarını destekler select count(distinct(a, b))? : D
Vytenis Bivainis

@VytenisBivainis PostgreSQL'in biliyorum - hangi versiyondan beri emin değilim.
karmakaze

3

Sorgunuzla ilgili yanlış bir şey yok, ancak bunu şu şekilde de yapabilirsiniz:

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

3

Umarım bu çalışır prima vista üzerine yazıyorum

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId

7
Bunun son cevabı vermesi için, cevabı başka bir SELECT COUNT (*) FROM (...) içine almanız gerekir. Aslında bu cevap size saymak istediğiniz farklı değerleri listelemek için başka bir yol sunuyor. Orijinal çözümünüzden daha iyi değil.
Dave Costa

Teşekkürler Dave. Benim durumumda farklı yerine grubu kullanabileceğinizi biliyorum. Sadece bir sorgu kullanarak nihai sonucu almak merak ediyordum. Bence imkansız ama yanılıyor olabilirim.
Novitzky

3

Bu yaklaşımı kullandım ve benim için çalıştı.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Benim durumum için, doğru sonuç sağlar.


İki sütunla birlikte size ayrı değerlerin sayısını vermez. En azından MySQL 5.8'de değil.
Anwar Shaikh

Bu soru SQL Server olarak etiketlendi ve bu SQL Server sözdizimi değil
Tab Alleman

2

"DISTINCT" için yalnızca bir alanınız varsa, şunu kullanabilirsiniz:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

SET SHOWPLAN_ALL ON ile test edildiği gibi, orijinaliyle aynı sorgu planını döndürür. Ancak iki alan kullanıyorsunuz, böylece deli gibi bir şey deneyebilirsiniz:

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

ancak NULL'lar söz konusuysa sorun yaşarsınız. Ben sadece orijinal sorguyla devam ederdim.


Benden +1. Teşekkürler ama önerdiğim gibi benim sorgu ile sopa olacak. "Convert" kullanmak performansı daha da düşürebilir.
Novitzky

2

Kendi sorunum için Google'da aradığımda bunu buldum, DISTINCT nesnelerini sayarsanız, doğru sayıyı döndürdüğünüzü buldum (MySQL kullanıyorum)

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems

5
Yukarıdaki sorgu OP (ayrı aradığı şeyi daha farklı bir sonuç kümesi döndürür kombinasyonları arasında DocumentIdve DocumentSessionId). OP, MS SQL Server değil MySQL kullanıyorsa Alexander Kjäll zaten doğru cevabı gönderdi.
Anthony Geoghegan

1

Keşke MS SQL de COUNT (DISTINCT A, B) gibi bir şey yapabilir. Ama olamaz.

Bazı testler CHECKSUM () benzersiz değerler oluşturamadı sonra ilk başta JayTee cevap bana bir çözüm gibi görünüyordu. Bunun hızlı bir örneği hem CHECKSUM (31,467,519) hem de CHECKSUM (69,1120,823) 55 ile aynı cevabı vermektedir.

Sonra biraz araştırma yaptım ve Microsoft'un değişiklik algılama amacıyla CHECKSUM kullanılmasını önermediğini gördüm. Bazı forumlarda bazıları

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

ama bu da şaşırtıcı değil.

HASHBYTES () işlevini TSQL CHECKSUM bilmecinde önerildiği gibi kullanabilirsiniz . Bununla birlikte, bunun benzersiz sonuçlar döndürmeme şansı da düşüktür.

Kullanmanızı öneririm

SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems

1

Buna ne dersin,

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

Bu bize tüm DocumentId ve DocumentSessionId kombinasyonlarının sayısını verir


0

Benim için çalışıyor. Kehanette:

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

Jpql dilinde:

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

0

Ben benzer bir soru vardı ama ben sorgu ana sorgu karşılaştırma verileri ile bir alt sorgu oldu. gibi bir şey:

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

Bunun karmaşıklığını göz ardı ederek, orijinal soruda açıklanan çift alt sorgu ile alt sorgu içine a.code değerini alamadım fark ettim

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

Sonunda hile yapabileceğimi ve sütunları birleştirebileceğimi anladım:

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

Sonuçta işte bu


0

Sabit uzunlukta veri tipleri ile çalışıyorsanız, bunu binaryçok kolay ve çok hızlı bir şekilde yapabilirsiniz. Varsayım DocumentIdve DocumentSessionIdher ikisi de ints ve bu nedenle 4 bayt uzunluğunda ...

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

Benim spesifik problem bölmek için beni gerekli SUMtarafından COUNTbaşka yabancı anahtar ile gruplama ve bazen belirli değerleri veya tuşlar ile filtreleme, çeşitli yabancı anahtarların farklı kombinasyon ve bir tarih alanı. Tablo çok büyük ve bir alt sorgu kullanmak sorgu süresini önemli ölçüde artırdı. Ve karmaşıklık nedeniyle, istatistikler basitçe uygulanabilir bir seçenek değildi. CHECKSUMSolüsyon özellikle çeşitli veri türlerinin bir sonucu olarak, onun dönüşümde çok yavaş da oldu ve onun güvenilmezliğini riskini göze alamazdım.

Bununla birlikte, yukarıdaki çözümün kullanılması sorgu süresinde neredeyse hiç artış göstermemiştir (sadece ile karşılaştırıldığında SUM) ve tamamen güvenilir olmalıdır! Benzer bir durumda başkalarına yardımcı olmalı, bu yüzden buraya gönderiyorum.


-1

Sadece Sayım İşlevini İki Kez kullanabilirsiniz.

Bu durumda şöyle olur:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems

bu soruda gerektiği gibi yapmaz, her sütun için ayrı ayrı sayar
naviram

-1

Bu kod, farklı 2 parametre kullanır ve bu farklı değerler satır sayısına özgü satır sayısı sağlar. MySQL'de benim için bir cazibe gibi çalıştı.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.