Подсчет DISTINCT по нескольким столбцам


214

Есть ли лучший способ сделать запрос, подобный этому:

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

Мне нужно посчитать количество отдельных элементов из этой таблицы, но количество разных элементов превышает два столбца.

Мой запрос работает нормально, но мне было интересно, смогу ли я получить окончательный результат, используя только один запрос (без использования подзапроса)


Иордан Танев, Марк Брэкетт, RC - спасибо за ответы, это была хорошая попытка, но вам нужно проверить, что вы делаете, прежде чем писать в SO. Предоставленные вами запросы не эквивалентны моему запросу. Вы можете легко видеть, что у меня всегда есть скалярный результат, но ваш запрос возвращает несколько строк.
Новицкий,

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


Это хороший вопрос. Мне также было интересно, есть ли более простой способ сделать это
Anupam

Ответы:


73

Если вы пытаетесь улучшить производительность, вы можете попытаться создать постоянный вычисляемый столбец для хеш-значения или объединенного значения двух столбцов.

Как только он сохраняется, при условии, что столбец является детерминированным, и вы используете «нормальные» настройки базы данных, он может быть проиндексирован и / или может быть создана статистика для него.

Я считаю, что отдельный счет вычисляемого столбца будет эквивалентен вашему запросу.


4
Отличное предложение! Чем больше я читаю, тем больше осознаю, что SQL - это не только знание синтаксиса и функций, но и применение чистой логики ... Хотелось бы, чтобы у меня было 2 отзыва!
тумчаадитья

Слишком хорошее предложение. Это позволило мне написать ненужный код для этого.
Авраджит Рой

1
Не могли бы вы добавить пример или пример кода, чтобы показать больше о том, что это значит и как это сделать?
Jayqui

52

Изменить: Изменено из менее надежного запроса только контрольной суммы, я обнаружил способ сделать это (в SQL Server 2005), который работает довольно хорошо для меня, и я могу использовать столько столбцов, сколько мне нужно (добавляя их в функция CHECKSUM ()). Функция REVERSE () превращает целые числа в varchars, чтобы сделать их более надежными

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

1
+1 Хороший, отлично работает (когда у вас есть правильные типы столбцов для выполнения контрольной суммы на ...;)
Бернулли ИТ

8
С такими хэш-значениями, как Checksum (), существует небольшая вероятность того, что один и тот же хэш будет возвращен для разных входных данных, поэтому счет может быть очень незначительным. HashBytes () - еще меньший шанс, но все же не ноль. Если бы эти два идентификатора были int (32b), то «хэш без потерь» мог бы объединить их в bigint (64b), такой как Id1 << 32 + Id2.
crokusek

1
вероятность не так уж мала, особенно когда вы начинаете комбинировать столбцы (для этого и предполагалось). Мне был любопытен этот подход, и в конкретном случае контрольная сумма оказалась на 10% меньше. Если вы думаете об этом немного дольше, Checksum просто возвращает целое число, поэтому, если вы будете проверять контрольную сумму полного диапазона bigint, вы получите отчетливое число, примерно в 2 миллиарда раз меньше, чем на самом деле. -1
Пволдерс

Обновлен запрос, чтобы включить использование «REVERSE» для удаления вероятности дублирования
JayTee

4
Можем ли мы избежать CHECKSUM - можем ли мы просто объединить два значения вместе? Я полагаю, что рискует считать одним и тем же: («он», «искусство») == «слышать», «т»). Но я думаю, что это можно решить с помощью разделителя, как предлагает @APC (какое-то значение, которое не отображается ни в одном столбце), так что 'he | ​​art'! = 'Listen | t' Есть ли другие проблемы с простым "объединением" подходить?
Красный горох

32

Что вам больше не нравится в существующем запросе? Если вы обеспокоены тем, что 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 от меня. Спасибо за Ваш ответ. Мой запрос работает нормально, но мне было интересно, смогу ли я получить окончательный результат, используя только один запрос (без использования подзапроса)
Novitzky

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/en/group-by-functions.html#function_count-distinct


Это был вопрос SQL Server, и оба опубликованных вами варианта уже упоминались в следующих ответах на этот вопрос: stackoverflow.com/a/1471444/4955425 и stackoverflow.com/a/1471713/4955425 .
Сстан

1
FWIW, это почти работает в PostgreSQL; просто нужны дополнительные скобки:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph

14

Как насчет чего-то вроде:

выберите количество (*)
из
  (выберите количество (*) 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; если ваша база данных не поддерживает это, она может быть смоделирована в соответствии с предложением @ Oncel-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 делает - не знаю, с какой версии.
Кармаказе

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
Для того чтобы это дало окончательный ответ, вам нужно будет обернуть его в другой SELECT COUNT (*) FROM (...). По сути, этот ответ просто дает вам другой способ перечисления различных значений, которые вы хотите посчитать. Это не лучше, чем ваше оригинальное решение.
Дэйв Коста

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

3

Я использовал этот подход, и он сработал для меня.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Для моего случая это дает правильный результат.


Это не дает вам счет различных значений в сочетании двух столбцов. По крайней мере, не в MySQL 5.8.
Анвар Шейх

Этот вопрос помечен SQL Server, и это не синтаксис SQL Server
вкладка 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 от меня. Спасибо, но я буду придерживаться моего запроса, как вы предложили. Использование «convert» может еще больше снизить производительность.
Новицкий,

2

Я обнаружил это, когда гуглил по своей собственной проблеме, обнаружил, что если вы посчитаете объекты DISTINCT, вы получите верное правильное число (я использую MySQL)

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

5
Этот запрос возвращает другой набор результатов , чем то , что ОП искал (в различных комбинациях с DocumentIdи DocumentSessionId). Александр Кьялл уже опубликовал правильный ответ, если OP использовал MySQL, а не MS SQL Server.
Энтони Геогеган

1

Я хотел бы, чтобы MS SQL мог также делать что-то вроде COUNT (DISTINCT A, B). Но это не может.

Сначала ответ JayTee показался мне решением, но после некоторых тестов CHECKSUM () не смог создать уникальные значения. Быстрый пример: и CHECKSUM (31 467 519), и CHECKSUM (69,11 8 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

это не делает так, как требуется в вопросе, оно подсчитывает отдельное для каждого столбца
naviram

-1

Этот код использует отличные по 2 параметрам и обеспечивает подсчет количества строк, специфичных для этих разных значений. Это работало для меня в 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.