2009-09-24 15 views
133

Czy istnieje lepszy sposób kwerendy tak:Zliczanie DISTINCT nad wielu kolumn

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

muszę policzyć liczbę odrębnych elementów z tej tabeli, ale wyraźny jest na dwóch kolumnach.

Moja kwerenda działa poprawnie, ale zastanawiałem się, czy mogę uzyskać ostateczny wynik przy użyciu tylko jednej kwerendy (bez użycia sub-query)

+0

IordanTanev, Mark Brackett, RC - dzięki za odpowiedzi, była to miła próba, ale przed wysłaniem do SO należy sprawdzić, co robisz. Podane zapytania nie są równoważne z moim zapytaniem. Możesz łatwo zobaczyć, że zawsze mam wynik skalarny, ale zapytanie zwraca wiele wierszy. – Novitzky

+0

Właśnie zaktualizowałem pytanie, aby dodać komentarz wyjaśniający z jednej z odpowiedzi. – Jeff

Odpowiedz

45

Jeśli chcesz, aby zwiększyć wydajność, można spróbować stworzenie trwało kolumna obliczana na obu mieszania lub wartości połączonego z dwóch kolumn.

Po utracie ważności, pod warunkiem, że kolumna jest deterministyczna i używasz "zdrowych" ustawień bazy danych, może ona zostać zaindeksowana i/lub można na niej utworzyć statystyki.

Wierzę, że wyraźna liczba obliczonej kolumny byłaby odpowiednikiem zapytania.

+3

Doskonała sugestia! Więcej sugestii! Czytam, tym bardziej zdaję sobie sprawę, że SQL jest mniejszy o znajomości składni i funkcji, a więcej o stosowaniu czystej logiki .. Żałuję, że nie miałem 2 upvotes! – tumchaaditya

+0

Zbyt dobra sugestia. Uniknęło mnie napisanie niepotrzebnego kodu do tego –

2

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 innym SELECT COUNT (*) FROM (...). Zasadniczo ta odpowiedź daje tylko inny sposób na wyliczenie różnych wartości, które chcesz liczyć. To nie jest lepsze niż oryginalne rozwiązanie. –

+0

Dzięki Dave. Wiem, że możesz używać grupy zamiast w moim przypadku inaczej. Zastanawiam się, czy uzyskasz końcowy wynik, używając tylko jednego zapytania. Myślę, że to niemożliwe, ale mogę się mylić. – Novitzky

16

Co ci się nie podoba w istniejącym zapytaniu? Jeśli obawiasz się, że DISTINCT w dwóch kolumnach nie zwraca tylko unikalnych permutacji, dlaczego nie spróbować?

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> 

edit

Zeszłam w ślepy zaułek z analityki ale odpowiedź była żenująco oczywiste ...

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

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

SQL> 

edit 2

Biorąc pod uwagę następujące dane powyższe rozwiązanie konkatenacji spowoduje błędne oszacowanie:

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

więc obejmować separator ...

select col1 + '*' + col2 from t23 
/

Oczywiście wybrany separator musi być znak lub zestaw znaków, który nigdy nie może pojawić się w każdej kolumnie.

+0

+1 ode mnie. Dzięki za odpowiedź. Moje zapytanie działa dobrze, ale zastanawiałem się, czy mogę uzyskać końcowy wynik przy użyciu tylko jednego zapytania (bez korzystania z podkwerendy). – Novitzky

11

Jak o czymś takim:

 
select count(*) 
from 
    (select count(*) cnt 
    from DocumentOutputItems 
    group by DocumentId, DocumentSessionId) t1 

Prawdopodobnie po prostu robi to samo, jak jesteś już tam, ale unika się wyraźne.

+0

Tak, masz rację. Wykonuje tę samą pracę, co moja oryginalna. – Novitzky

+0

w moich testach (przy użyciu SET SHOWPLAN_ALL ON), miał ten sam plan wykonania i dokładnie ten sam TotalSubtreeCost –

+0

+1 dla miłej próby i wyjaśnienia. – Novitzky

6

Oto krótsza wersja bez podselekcji:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems 

Współpracuje w MySQL, i myślę, że optymalizator ma łatwiej zrozumieć ten jeden.

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

+5

w SQL Server otrzymasz: __Msg 102, Poziom 15, Stan 1, Wiersz 1 Niepoprawna składnia w pobliżu ',' .__ –

+0

Oto, o czym myślałem. Chcę zrobić coś podobnego w MSSQL, jeśli to możliwe. – Novitzky

+0

@Kamil Nowicki, w SQL Server, możesz mieć tylko jedno pole w COUNT(), w mojej odpowiedzi pokażę, że możesz połączyć dwa pola w jeden i wypróbować to podejście. Jednak po prostu trzymałbym się oryginału, ponieważ plany zapytania zakończyłyby się tak samo. –

2

gdybyś miał tylko jedno pole do „odrębne”, można użyć:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems 

i że nie wraca ten sam plan kwerend jak oryginał, jak testowane SET SHOWPLAN_ALL ON. Jednak używasz dwóch pól, więc możesz spróbować czegoś szalonego, jak:

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

, ale będziesz mieć problemy, jeśli zaangażowane są NULL.Po prostu trzymałem się oryginalnego zapytania.

+0

+1 ode mnie.Dzięki, ale będę trzymać się mojego zapytania, jak zasugerował.Korzystanie z "konwertować" może zmniejszyć wydajność jeszcze bardziej. – Novitzky

3

Nie ma nic złego w zapytaniu, ale można też zrobić to w ten sposób:

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

Edit: Altered z mniej niż wiarygodne kontrolna tylko zapytania odkryłem sposób, aby to zrobić (w SQL Server 2005), który działa całkiem dobrze dla mnie i mogę wykorzystać taką liczbę kolumn Potrzebuję (dodając je do funkcji CHECKSUM()). Funkcja REVERSE() zamienia ints do varchars aby odrębne bardziej niezawodny

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

+ 1 Nice, działa idealnie (gdy masz odpowiednie typy kolumn do wykonania CheckSum w ...;) –

+7

Z hasłami takimi jak Checksum(), jest mała szansa, że ​​ten sam skrót zostanie zwrócony dla różnych danych wejściowych, więc liczba może być bardzo nieznacznie wyłączona. HashBytes() jest jeszcze mniejszą szansą, ale wciąż nie ma zera. Jeśli te dwa identyfikatory były int (32b), wówczas "bezstratny hash" mógłby połączyć je w bigint (64b), taki jak Id1 << 32 + Id2. – crokusek

+1

Szansa nie jest tak mała nawet, zwłaszcza gdy zaczynasz łączyć kolumny (to jest to, za co miał być przeznaczony). Byłem ciekawy tego podejścia, aw konkretnym przypadku suma kontrolna zakończyła się liczbą mniejszą o 10%. Jeśli pomyślisz o tym nieco dłużej, suma kontrolna po prostu zwraca wartość int, więc jeśli suma kontrolna będzie równa sumie biginta, otrzymasz odrębną liczbę około 2 miliardów razy mniejszą niż jest w rzeczywistości. -1 – pvolders

1

życzę MS SQL mógł również zrobić coś takiego COUNT (DISTINCT A, B). Ale nie może.

Na początku odpowiedź JayTee wydawała mi się rozwiązaniem dla mnie po niektórych testach CHECKSUM() nie zdołał stworzyć unikalnych wartości. Szybki przykład: zarówno CHECKSUM (31.467.519), jak i CHECKSUM (69.120.2823) dają taką samą odpowiedź, która wynosi 55.

Potem dokonałem pewnych badań i odkryłem, że Microsoft NIE zaleca używania CHECKSUM do celów wykrywania zmian. Na niektórych forach niektórzy sugerowali używanie tego samego, ale nie jest to również komfortowe.

Możesz użyć funkcji HASHBYTES() zgodnie z sugestią podaną w TSQL CHECKSUM conundrum. Ma to jednak niewielką szansę na uniknięcie unikalnych wyników.

Sugerowałbym użyciu

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

Znalazłem to kiedy Googled dla własnego problemu, okazało się, że jeśli liczyć Państwo odrębnych obiektów, można uzyskać prawidłowy numer zwrócony (używam MySQL)

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

Powyższe zapytanie zwróci inny zestaw wyników niż to, czego szukał OP (różne kombinacje ** ** z 'DocumentId' i ' DocumentSessionId'). Alexander Kjäll już opublikował poprawną odpowiedź, jeśli OP był przy użyciu MySQL, a nie MS SQL Server. –

4

Aby uruchomić jako pojedyncze zapytanie, należy połączyć kolumny, a następnie uzyskać odrębną liczbę wystąpień połączonego ciągu.

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

W MySQL można zrobić to samo bez etapu konkatenacji następująco:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems; 

Ta funkcja jest wymieniony w dokumentacji MySQL:

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

+0

To było pytanie SQL Server, a obie opcje, które wysłałeś, zostały już wymienione w następujących odpowiedziach na to pytanie: http://stackoverflow.com/a/1471444/4955425 i http://stackoverflow.com/a/1471713/4955425. – sstan

-2

Zostało postawione i odpowiedział na Quora (https://www.quora.com/In-SQL-how-to-I-count-DISTINCT-over-multiple-columns):

select col1, col2, col3, count(*) 
from table 
group by col1, col2, col3 

Pracowałem nad tym w SAS, a SAS Proc SQL nie lubi DISTINCT z więcej niż jedną kolumną.

+0

Pierwotne zapytanie w pytaniu zwraca liczbę kombinacji w danych kolumnach. Ta odpowiedź zwraca liczbę wystąpień dla każdej kombinacji w danych kolumnach. – jumxozizi

-2

To poniżej kwerendy pracował dla mnie na MySQL:

SELECT COUNT(DISTINCT col_1,col_2,..) from table_name; 

Kolumny podane w powyższym mianowicie zapytań. col_1, col_2 ma połączone ograniczenie UNIQUE. Oznacza to, że w mojej tabeli table_name utworzyłem indeks UNIQUE na col_1 + col_2.