Підрахунок DISTINCT у кількох стовпцях


213

Чи є кращий спосіб зробити такий запит:

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

Мені потрібно порахувати кількість окремих елементів з цієї таблиці, але відмінність - понад два стовпці.

Мій запит працює нормально, але мені було цікаво, чи зможу я отримати остаточний результат, використовуючи лише один запит (без використання підзапиту)


IordanTanev, Mark Brackett, RC - дякую за відповіді, це була приємна спроба, але вам потрібно перевірити, що ви робите, перш ніж відправляти повідомлення в SO. Надані вами запити не еквівалентні моєму запиту. Ви легко бачите, що у мене завжди є скалярний результат, але ваш запит повертає кілька рядків.
Новицький

Щойно оновив питання, щоб включити свій уточнюючий коментар до однієї з відповідей
Джефф


Це гарне запитання. Мені теж було цікаво, чи існує простіший спосіб зробити це
Анупам

Відповіді:


73

Якщо ви намагаєтеся підвищити ефективність, ви можете спробувати створити збережений обчислюваний стовпець або на хеш, або на об'єднане значення двох стовпців.

Після збереження, за умови, що стовпець є детермінованим і ви використовуєте "здорові" налаштування бази даних, його можна індексувати та / або створювати статистику на ньому.

Я вважаю, що окремий підрахунок обчисленого стовпця буде еквівалентний вашому запиту.


4
Відмінна пропозиція! Чим більше я читаю, тим більше я розумію, що SQL менше знає синтаксис та функції та більше застосовує чисту логіку. Мені б хотілося, щоб у мене було 2 оновлення!
tumchaaditya

Занадто гарна пропозиція. Це дозволило мені писати непотрібний код до цього.
Авраїт Рой

1
Будь ласка, додайте приклад чи зразок коду, щоб показати більше про те, що це означає і як це зробити?
jayqui

52

Редагувати: Змінено з менш надійного запиту лише контрольної суми, я знайшов спосіб зробити це (у SQL Server 2005), який працює для мене досить добре, і я можу використовувати стільки стовпців, скільки мені потрібно (додавши їх до функція CHECKSUM (). Функція REVERSE () перетворює вкладки на варшари, щоб зробити чіткіше більш надійним

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

1
+1 Приємно, працює ідеально (коли у вас є правильні типи стовпців для виконання CheckSum на ...;)
Bernoulli IT

8
У хешах, таких як Checksum (), є невелика ймовірність того, що той самий хеш буде повернутий для різних входів, тому кількість може бути злегка відключена. HashBytes () - ще менший шанс, але все ще не нульовий. Якщо ці два ідентифікатори були int's (32b), то "хеш без втрат" міг би об'єднати їх у bigint (64b) на зразок Id1 << 32 + Id2.
crokusek

1
шанс навіть не такий малий, особливо коли ви починаєте поєднувати стовпці (саме це і повинно було бути призначено). Мені було цікаво про цей підхід, і в конкретному випадку контрольна сума закінчилася на 10% менше. Якщо ви подумаєте про це трохи довше, Checksum просто повертає int, тож якщо ви будете контрольною сумою повного діапазону bigint, ви в кінцевому підсумку з чітким рахунком приблизно в 2 мільярди разів меншим, ніж є насправді. -1
pvolders

Оновлено запит, щоб включити використання "REVERSE" для видалення шансів на копії
JayTee

4
Чи можемо ми уникнути ЧЕКСУМУ - чи могли ми просто об'єднати ці два значення разом? Я гадаю, що ризикує розглядати як те саме: ('він', 'мистецтво') == 'чути', 't'). Але я думаю, що це можна вирішити за допомогою роздільника, як пропонує @APC (якесь значення, яке не відображається в жодному стовпці), тож "він | мистецтво"! = "Чути | т" Чи є інші проблеми з простою "конкатенацією" підхід?
Червоний горох

31

Що з вашого запиту, який вам не подобається? Якщо ви стурбовані тим, що DISTINCTчерез два стовпці не повертаються лише унікальні перестановки, чому б не спробувати його?

Це, безумовно, працює, як ви могли очікувати в 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>

редагувати

Я пішов сліпою алеєю з аналітикою, але відповідь була гнітюче очевидною ...

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

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

SQL>

редагувати 2

З огляду на наступні дані, приведене вище рішення для поєднання:

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

Отже, ми повинні включити роздільник ...

select col1 + '*' + col2 from t23
/

Очевидно, що обраний роздільник повинен бути символом або набором символів, який ніколи не може відображатися в жодному стовпці.


+1 від мене. Дякую за вашу відповідь. Мій запит працює нормально, але мені було цікаво, чи зможу я отримати остаточний результат, використовуючи лише один запит (без використання
підпиту

20

Щоб запуститись як один запит, об'єднайте стовпці, а потім отримайте чіткий підрахунок екземплярів об'єднаного рядка.

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

У MySQL ви можете зробити те саме, що не відбувається крок конкатенації, як описано нижче:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Ця функція згадується в документації на MySQL:

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


Це було питання про SQL Server, і обидва опубліковані вами варіанти вже згадувалися в наступних відповідях на це питання: stackoverflow.com/a/1471444/4955425 та stackoverflow.com/a/1471713/4955425 .
sstan

1
FWIW, це майже працює в PostgreSQL; просто потрібні додаткові дужки:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

Як щодо чогось такого:

кількість вибору (*)
з
  (виберіть count (*) cnt
   з DocumentOutputItems
   групувати по DocumentId, DocumentSessionId) t1

Напевно, просто робить те саме, що ви вже хоч, але це уникає ДИСТИНКТ.


у моїх тестах (використовуючи SET SHOWPLAN_ALL ON) він мав той самий план виконання та точно такий же TotalSubtreeCost
KM.

1
Залежно від складності оригінального запиту, вирішення цього питання GROUP BYможе спричинити кілька додаткових викликів для трансформації запитів для досягнення бажаного результату (наприклад, коли в оригінальному запиті вже були GROUP BYабо HAVINGпункти ...)
Лукаш Едер

8

Ось коротша версія без підселектора:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Це добре працює в MySQL, і я думаю, що оптимізатору простіше зрозуміти цей.

Редагувати: Мабуть, я неправильно прочитав MSSQL та MySQL - вибачте за це, але, можливо, це все одно допомагає.


6
у SQL Server ви отримуєте: Msg 102, рівень 15, стан 1, рядок 1 Неправильний синтаксис поблизу ','.
КМ.

Це те, про що я думав. Я хочу зробити подібне в MSSQL, якщо можливо.
Новицький

@Kamil Nowicki, у SQL Server ви можете мати лише одне поле в COUNT (), у своїй відповіді я показую, що ви можете об'єднати два поля в одне і спробувати такий підхід. Однак я б просто дотримувався оригіналу, оскільки плани запитів закінчилися б такими ж.
КМ.

1
Будь ласка, подивіться у відповідь @JayTee. Це працює як шарм. count ( distinct CHECKSUM ([Field1], [Field2])
Кастодіо

5

Багато (найбільше?) Баз даних SQL можуть працювати з кортежами, такими як значення, так що ви можете просто зробити: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Якщо ваша база даних не підтримує це, це може бути імітовано згідно з пропозицією @ onel-umut-turer про CHECKSUM або іншою скалярною функцією, що забезпечує гарну унікальність напр COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Пов'язане використання кортежів - це виконання INзапитів, таких як: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));


які бази даних підтримують select count(distinct(a, b))? : D
Вітеніс Бівайніс

@VytenisBivainis Я знаю, що робить PostgreSQL - не впевнений, з якої версії.
karmakaze

3

У вашому запиті нічого поганого, але ви також можете зробити це так:

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

3

Сподіваюся, це працює, я пишу на prima vista

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

7
Для того, щоб дати остаточну відповідь, вам доведеться загорнути її в інший ВИБІР КОЛЕТУ (*) ВІД (...). По суті, ця відповідь просто дає вам ще один спосіб перерахувати різні значення, які ви хочете порахувати. Це не краще, ніж ваше оригінальне рішення.
Дейв Коста,

Дякую, Дейв. Я знаю, що ви можете використовувати групу, а не окремо в моєму випадку. Мені було цікаво, чи отримаєте ви остаточний результат, використовуючи лише один запит. Я думаю, що це неможливо, але я можу помилитися.
Новицький

3

Я використовував такий підхід, і він спрацював на мене.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

У моєму випадку це дає правильний результат.


Це не дає вам підрахунку чітких значень у поєднанні двох стовпців. Принаймні, не в MySQL 5.8.
Анвар Шейх

Це питання позначено тегом SQL Server, і це не синтаксис SQL Server
Tab Alleman

2

якщо у вас було лише одне поле "DISTINCT", ви можете використовувати:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

і це повертає той самий план запиту, що і оригінал, як це перевірено за допомогою параметра SET SHOWPLAN_ALL ON. Однак ви використовуєте два поля, щоб ви могли спробувати щось божевільне:

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

але у вас виникнуть проблеми, якщо задіяні NULL. Я б просто дотримувався оригінального запиту.


+1 від мене. Дякую, але я дотримаюсь свого запиту, як ви запропонували. Використання "конвертувати" може ще більше знизити продуктивність.
Новицький

2

Я виявив це, коли переглянув свою проблему з Google, і виявив, що якщо порахувати об'єкти DISTINCT, ви отримаєте повернене правильне число (я використовую MySQL)

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

5
Цей запит повертає інший набір результатів , ніж те , що ОП шукав (в різних комбінаціях з DocumentIdі DocumentSessionId). Олександр Кялл вже опублікував правильну відповідь, якщо ОП використовував MySQL, а не MS SQL Server.
Ентоні Геоґеган

1

Я хочу, щоб MS SQL також міг зробити щось на кшталт COUNT (DISTINCT A, B). Але це не може.

Спочатку відповідь JayTee видалася мені вирішенням після того, як деякі тести CHECKSUM () не змогли створити унікальні значення. Швидкий приклад: і CHECKSUM (31,467,519), і CHECKSUM (69,1120,823) дають однакову відповідь, яка є 55.

Тоді я провів деякі дослідження і виявив, що Microsoft НЕ рекомендує використовувати CHECKSUM для виявлення змін. На деяких форумах деякі пропонували використовувати

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

але це теж не задобрює.

Ви можете використовувати функцію HASHBYTES (), як це запропоновано в загадці TSQL CHECKSUM . Однак це також має невеликі шанси не повернути унікальних результатів.

Я б запропонував використовувати

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

1

Як щодо цього,

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

Це дозволить нам підрахувати всі можливі комбінації DocumentId і DocumentSessionId


0

Це працює для мене. В оракулі:

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

У jpql:

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

0

У мене було подібне запитання, але запит, який у мене був, був підзапитом із даними порівняння в основному запиті. щось на зразок:

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

ігноруючи складності цього, я зрозумів, що не можу отримати значення a.code у підзапиті з подвійним підзапитом, описаним у вихідному запитанні

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

Отже, врешті-решт я зрозумів, що можу обдурити, і комбінувати стовпці:

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

Це те, що закінчилось


0

Якщо ви працюєте з типами даних фіксованої довжини, ви можете binaryзробити це дуже легко і дуже швидко. Припускаючи , DocumentIdі DocumentSessionIdобидва ints, і тому 4 байтам ...

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

Моя конкретна проблема вимагає , щоб я розділити SUMна COUNTз окремою комбінації різних зовнішніх ключів і поля дати, групуючи інший зовнішнім ключем , а іноді фільтрації певних значень або ключами. Таблиця дуже велика, а використання підзапиту різко збільшило час запиту. А через складність, статистика просто не була життєздатною можливістю. CHECKSUMРішення було також занадто повільно його перетворення, в зокрема , в результаті різних типів даних, і я не міг ризикувати своїм ненадійності.

Однак використання вищевказаного рішення практично не збільшувало час запиту (порівняно з використанням просто SUM), і повинно бути повністю надійним! Він повинен бути в змозі допомогти іншим у подібній ситуації, тому я публікую його тут.


-1

Ви можете просто використовувати функцію підрахунку двічі.

У цьому випадку це було б:

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

це не так, як вимагають у запитанні, він рахує різницю окремо для кожного стовпця
навірам

-1

Цей код використовує розрізнення за двома параметрами та забезпечує підрахунок кількості рядків, характерних для цих окремих значень рядків. Це працювало для мене в MySQL як шарм.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.