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)


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.


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

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.


+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


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.


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.


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'));


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

3

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

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

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.


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.


+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

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

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


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;

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


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.


-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

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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.