Zliczanie DISTINCT w wielu kolumnach

213

Czy istnieje lepszy sposób wykonania takiego zapytania:

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

Muszę policzyć liczbę odrębnych pozycji z tej tabeli, ale odrębność dotyczy dwóch kolumn.

Moje zapytanie działa dobrze, ale zastanawiałem się, czy mogę uzyskać końcowy wynik za pomocą tylko jednego zapytania (bez użycia pod-zapytania)

Nowicki
źródło
IordanTanev, Mark Brackett, RC - dzięki za odpowiedzi, to była fajna próba, ale musisz sprawdzić, co robisz przed wysłaniem do SO. Podane przez ciebie zapytania nie są równoważne z moim zapytaniem. Możesz łatwo zobaczyć, że zawsze mam wynik skalarny, ale zapytanie zwraca wiele wierszy.
Novitzky,
Właśnie zaktualizowałem pytanie, dodając wyjaśniający komentarz do jednej z odpowiedzi
Jeff
To dobre pytanie. Zastanawiałem się również, czy istnieje prostszy sposób, aby to zrobić
Anupam

Odpowiedzi:

73

Jeśli próbujesz poprawić wydajność, możesz spróbować utworzyć utrwaloną kolumnę obliczeniową na podstawie wartości skrótu lub konkatenacji dwóch kolumn.

Po utrwaleniu, pod warunkiem, że kolumna jest deterministyczna i używasz „zdrowych” ustawień bazy danych, można ją indeksować i / lub tworzyć na niej statystyki.

Uważam, że odrębna liczba obliczonych kolumn byłaby równoważna z zapytaniem.

Jason Horner
źródło
4
Doskonała sugestia! Im więcej czytam, tym bardziej zdaję sobie sprawę, że SQL nie polega na znajomości składni i funkcji, a bardziej na stosowaniu czystej logiki. Chciałbym mieć 2 głosy poparcia!
tumchaaditya
Zbyt dobra sugestia. Unikałem pisania niepotrzebnego kodu do tego.
Avrajit Roy
1
Czy możesz dodać przykład lub przykładowy kod, aby pokazać więcej o tym, co to znaczy i jak to zrobić?
jayqui
52

Edycja: Zmieniony z mniej niż wiarygodnego zapytania zawierającego tylko sumę kontrolną Odkryłem sposób na to (w SQL Server 2005), który działa całkiem dobrze dla mnie i mogę użyć tyle kolumn, ile potrzebuję (dodając je do funkcja CHECKSUM ()). Funkcja REVERSE () przekształca ints w varchary, aby uczynić wyróżnienie bardziej niezawodnym

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems
JayTee
źródło
1
+1 Fajny, działa idealnie (gdy masz odpowiednie typy kolumn, aby wykonać CheckSum w dniu ...;)
Bernoulli IT
8
W przypadku skrótów, takich jak Checksum (), istnieje niewielka szansa, że ​​ten sam skrót zostanie zwrócony dla różnych danych wejściowych, więc zliczanie może być bardzo nieznacznie wyłączone. HashBytes () to jeszcze mniejsza szansa, ale wciąż nie zero. Gdyby te dwa identyfikatory były liczbami całkowitymi (32b), wówczas „bezstratny skrót” mógłby połączyć je w bigint (64b), taki jak Id1 << 32 + Id2.
crokusek
1
szansa nie jest nawet taka mała, szczególnie, gdy zaczynasz łączyć kolumny (do czego to miało być przeznaczone). Byłem ciekawy tego podejścia, aw konkretnym przypadku suma kontrolna skończyła się o 10% mniej. Jeśli pomyślisz o tym trochę dłużej, suma kontrolna po prostu zwraca liczbę całkowitą, więc jeśli suma kontrolna będzie miała pełny zakres bigint, otrzymasz wyraźną liczbę około 2 miliardów razy mniejszą niż w rzeczywistości. -1
pvolders
Zaktualizowano zapytanie, aby uwzględnić użycie „ODWRÓCENIA” w celu usunięcia szansy na duplikaty
JayTee
4
Czy moglibyśmy uniknąć CHECKSUM - czy moglibyśmy po prostu połączyć te dwie wartości razem? Podejrzewam, że takie ryzyko można uznać za to samo: („on”, „art”) == „usłyszeć”, „t”). Ale myślę, że można to rozwiązać za pomocą separatora, jak proponuje @APC (pewna wartość, która nie pojawia się w żadnej kolumnie), więc „he | art”! = 'Usłyszeć | t' Czy są inne problemy z prostym „łączeniem” podejście?
The Red Pea
31

Co takiego jest w twoim istniejącym zapytaniu, czego nie lubisz? Jeśli się tym martwiszDISTINCT dwie kolumny nie zwracają tylko unikatowych permutacji, spróbuj tego?

Z pewnością działa tak, jak można oczekiwać w 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>

edytować

Poszedłem ślepą uliczką z analizami, ale odpowiedź była przygnębiająco oczywista ...

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

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

SQL>

edycja 2

Biorąc pod uwagę następujące dane, powyższe rozwiązanie konkatenacyjne zostanie przelane:

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

Więc dołączymy separator ...

select col1 + '*' + col2 from t23
/

Oczywiście wybrany separator musi być znakiem lub zestawem znaków, które nigdy nie mogą pojawić się w żadnej kolumnie.

APC
źródło
+1 ode mnie Dziękuję za odpowiedź. Moje zapytanie działa dobrze, ale zastanawiałem się, czy mogę uzyskać wynik końcowy za pomocą tylko jednego zapytania (bez użycia podzapytania)
Novitzky 24.09.2009
19

Aby uruchomić jako pojedyncze zapytanie, połącz kolumny, a następnie uzyskaj odrębną liczbę wystąpień połączonego łańcucha.

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

W MySQL możesz zrobić to samo bez kroku konkatenacji w następujący sposób:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Ta funkcja jest wspomniana w dokumentacji MySQL:

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

spelunk1
źródło
To było pytanie dotyczące programu SQL Server i obie opcje, które opublikowałeś, zostały już wspomniane w następujących odpowiedziach na to pytanie: stackoverflow.com/a/1471444/4955425 i stackoverflow.com/a/1471713/4955425 .
sstan
1
FWIW, to prawie działa w PostgreSQL; po prostu potrzebuję dodatkowych nawiasów:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph
14

Co powiesz na coś takiego:

wybierz liczbę (*)
z
  (wybierz liczbę (*) cnt
   z DocumentOutputItems
   grupuj według DocumentId, DocumentSessionId) t1

Prawdopodobnie robi to samo, co już jesteś, ale unika to DISTINCT.

Szpilki Trevora
źródło
w moich testach (używając SET SHOWPLAN_ALL ON) miał ten sam plan wykonania i dokładnie taki sam TotalSubtreeCost
KM.
1
W zależności od złożoności pierwotnego zapytania, jego rozwiązanie GROUP BYmoże wprowadzić kilka dodatkowych wyzwań do transformacji zapytania w celu uzyskania pożądanego wyniku (np. Gdy pierwotne zapytanie już zawierało GROUP BYlub zawierało HAVINGklauzule ...)
Lukas Eder,
8

Oto krótsza wersja bez podselekcji:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Działa dobrze w MySQL i myślę, że optymalizator ma łatwiejszy czas na zrozumienie tego.

Edycja: Najwyraźniej źle odczytałem MSSQL i MySQL - przepraszam za to, ale może i tak to pomaga.

Alexander Kjäll
źródło
6
w SQL Server otrzymujesz: Msg 102, poziom 15, stan 1, wiersz 1 Niepoprawna składnia w pobliżu ','.
KM.
Właśnie o tym myślałem. Chcę zrobić podobne rzeczy w MSSQL, jeśli to możliwe.
Novitzky
@Kamil Nowicki, w SQL Server możesz mieć tylko jedno pole w COUNT (), w mojej odpowiedzi pokazuję, że możesz połączyć dwa pola w jedno i wypróbować to podejście. Jednak pozostanę przy oryginale, ponieważ plany zapytań skończyłyby się tak samo.
KM.
1
Proszę spojrzeć na odpowiedź @JayTee. To działa jak urok. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio
5

Wiele (większość?) Baz danych SQL może współpracować z krotkami takimi jak wartości, więc możesz po prostu to zrobić: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; jeśli twoja baza danych tego nie obsługuje, można ją zasymulować zgodnie z sugestią @ oncel-umut-turer dotyczącą CHECKSUM lub innej funkcji skalarnej zapewniającej dobrą wyjątkowość np COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Powiązanym zastosowaniem krotek jest wykonywanie INzapytań, takich jak: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

karmakaze
źródło
jakie bazy danych obsługują select count(distinct(a, b))? : D
Vytenis Bivainis,
@VytenisBivainis Wiem, że PostgreSQL nie - nie jestem pewien, od której wersji.
karmakaze
3

W zapytaniu nie ma nic złego, ale możesz to zrobić w ten sposób:

WITH internalQuery (Amount)
AS
(
    SELECT (0)
      FROM DocumentOutputItems
  GROUP BY DocumentId, DocumentSessionId
)
SELECT COUNT(*) AS NumberOfDistinctRows
  FROM internalQuery
Bliek
źródło
3

Mam nadzieję, że to działa Piszę na prima vista

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId
IordanTanev
źródło
7
Aby uzyskać ostateczną odpowiedź, musisz zawinąć ją w inny WYBIERZ LICZBĘ (*) OD (...). Zasadniczo ta odpowiedź daje ci tylko inny sposób wyszczególnienia różnych wartości, które chcesz policzyć. To nie jest lepsze niż twoje oryginalne rozwiązanie.
Dave Costa
Dzięki Dave. Wiem, że w moim przypadku możesz użyć grupowania zamiast odrębnego. Zastanawiałem się, czy uzyskasz końcowy wynik za pomocą tylko jednego zapytania. Myślę, że jest to niemożliwe, ale mogę się mylić.
Novitzky
3

Zastosowałem to podejście i zadziałało dla mnie.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

W moim przypadku zapewnia prawidłowy wynik.

Jaanis Veinberg
źródło
Nie podaje liczby odrębnych wartości w połączeniu z dwiema kolumnami. Przynajmniej nie w MySQL 5.8.
Anwar Shaikh,
To pytanie jest oznaczone jako SQL Server i nie jest to składnia SQL Server
Tab Alleman
2

jeśli masz tylko jedno pole do „DISTINCT”, możesz użyć:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

i to zwraca ten sam plan zapytań co oryginał, co przetestowano przy użyciu SET SHOWPLAN_ALL ON. Jednak używasz dwóch pól, aby wypróbować coś szalonego:

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

ale będziesz mieć problemy, jeśli zaangażowane są wartości NULL. Pozostałbym przy pierwotnym zapytaniu.

KM.
źródło
+1 ode mnie Dzięki, ale pozostanę przy swoim zapytaniu, jak zasugerowałeś. Użycie „konwersji” może jeszcze bardziej obniżyć wydajność.
Novitzky
2

Znalazłem to, gdy poszukałem Google'a w związku z moim problemem, stwierdziłem, że jeśli policzysz obiekty DISTINCT, otrzymasz poprawną liczbę (używam MySQL)

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems
tehaugmenter
źródło
5
Powyższe zapytanie zwróci inny zestaw wyników niż PO szukał (odrębnych kombinacji z DocumentIda DocumentSessionId). Alexander Kjäll już opublikował poprawną odpowiedź, jeśli OP używa MySQL, a nie MS SQL Server.
Anthony Geoghegan
1

Chciałbym, aby MS SQL mógł zrobić coś w rodzaju COUNT (DISTINCT A, B). Ale nie może.

Na początku odpowiedź JayTee wydawała mi się rozwiązaniem po kilku testach CHECKSUM () nie udało się stworzyć unikalnych wartości. Szybki przykład jest taki, że zarówno CHECKSUM (31 467 519), jak i CHECKSUM (69 1120 823) daje tę samą odpowiedź, która wynosi 55.

Później przeprowadziłem badania i odkryłem, że Microsoft NIE zaleca korzystania z funkcji CHECKSUM do wykrywania zmian. Na niektórych forach niektórzy sugerowali użycie

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

ale to również nie jest wygodne.

Możesz użyć funkcji HASHBYTES () zgodnie z sugestią w zagadce TSQL CHECKSUM . Ma to jednak niewielką szansę na nie zwrócenie unikatowych wyników.

Sugerowałbym użycie

SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems
Oncel Umut TURER
źródło
1

Co powiesz na to,

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

Otrzymamy w ten sposób liczbę wszystkich możliwych kombinacji DocumentId i DocumentSessionId

Nikhil Singh
źródło
0

Mi to pasuje. W wyroczni:

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

W jpql:

SELECT SUM(CASE WHEN COUNT(i)=1 THEN 1 ELSE 1 END)
FROM DocumentOutputItems i GROUP BY i.DocumentId, i.DocumentSessionId;
Nata
źródło
0

Miałem podobne pytanie, ale zapytanie, które zadałem, było zapytaniem cząstkowym z danymi porównawczymi w zapytaniu głównym. coś jak:

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

ignorując złożoność tego, zdałem sobie sprawę, że nie mogę wprowadzić wartości a.code do podzapytania za pomocą podwójnego zapytania podrzędnego opisanego w pierwotnym pytaniu

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

W końcu doszedłem do wniosku, że mogę oszukiwać i łączyć kolumny:

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

To właśnie skończyło się na pracy

Mark Rogers
źródło
0

Jeśli pracujesz z typami danych o stałej długości, możesz rzutować, aby binaryto zrobić bardzo łatwo i bardzo szybko. Zakładając, DocumentIdi DocumentSessionIdto zarówno ints, a zatem są 4 bajty długo ...

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

Mój specyficzny problem wymagał ode mnie podzielenia SUMprzez COUNTodrębną kombinację różnych kluczy obcych i pola daty, grupowania według innego klucza obcego i czasami filtrowania według określonych wartości lub kluczy. Tabela jest bardzo duża, a użycie zapytania częściowego znacznie wydłużyło czas zapytania. Ze względu na złożoność statystyki po prostu nie były realną opcją. CHECKSUMRozwiązaniem było również zbyt powolne w jego konwersji, szczególnie w wyniku różnych typów danych, a ja nie mogłem ryzykować jego zawodność.

Jednak użycie powyższego rozwiązania praktycznie nie wydłużyło czasu zapytania (w porównaniu z użyciem po prostu SUM) i powinno być całkowicie niezawodne! Powinien być w stanie pomóc innym w podobnej sytuacji, więc zamieszczam to tutaj.

IphStich
źródło
-1

Możesz po prostu użyć funkcji Count Count dwa razy.

W takim przypadku byłoby to:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems
Bibek
źródło
to nie robi tego, co jest wymagane w pytaniu, liczy odrębne osobno dla każdej kolumny
naviram
-1

Ten kod używa odrębnego dla 2 parametrów i podaje liczbę wierszy charakterystyczną dla liczby różnych wierszy. W MySQL zadziałało to jak urok.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
rishi jain
źródło