Возвращает 0 в GROUP BY, когда COUNT(*) равен NULL

Вот мой исходный запрос:

SELECT
    CAST(IndexedDate as varchar),
    COUNT(*) AS Logins
FROM
    Table
WHERE
    EventType = 'Login'
AND IndexedDate > DATEADD(mm, -1, GETDATE())
GROUP BY
    IndexedDate
ORDER BY
    IndexedDate DESC

Это оставит пробелы, например:

2016-09-13    41
2016-09-12    31
2016-09-09    15
2016-09-08    36

Основываясь на этом вопросе, я попробовал следующие и все еще получили пробелы, но вдобавок ко всему результаты были неправильными (числа были НАМНОГО больше):

SELECT
    CAST(IndexedDate as varchar),
    SUM(Case When COUNT(*) Is Null Then 0 Else COUNT(*)  End) AS Logins
FROM
...

Как я могу сделать так, чтобы мои результаты выглядели так?

2016-09-13    41
2016-09-12    31
2016-09-11    0
2016-09-10    0
2016-09-09    15
2016-09-08    36

Я проверил несколько других вопросов, но все они связаны с соединениями или другими факторами, не входящими в мой сценарий.


ОБНОВЛЕНИЕ

Основываясь на комментариях, я попытался создать OUTER JOIN. Эта итерация, наконец, прошла успешно, но результаты были немного обратными...

SELECT
        CAST(a.IndexedDate as varchar) as dt,
        COUNT(*) AS Logins
FROM 
        (
        SELECT *
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        AND EventType = 'Login'
        ) a
FULL OUTER JOIN (
        SELECT DISTINCT(IndexedDate)
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        ) b
ON 
        a.IndexedDate = b.IndexedDate
GROUP BY
        b.IndexedDate
ORDER BY
        b.IndexedDate DESC

Полученные результаты:

2016-09-13    41
2016-09-12    31
(null)    1
(null)    1
2016-09-09    15
2016-09-08    36

Я проверил, что агрегат b включает недостающие даты.


person JOATMON    schedule 16.09.2016    source источник
comment
Внешнее соединение с таблицей календаря (содержащей все возможные даты).   -  person jarlh    schedule 16.09.2016
comment
аааа, логично. Я надеялся на более простой способ, но я думаю, вы не можете получить что-то из ничего.   -  person JOATMON    schedule 16.09.2016
comment
Второй ответ @jarlh. Это действительно единственный разумный способ сделать то, что вы хотите сделать.   -  person WillardSolutions    schedule 16.09.2016
comment
Следуя комментарию @jarlh, SQL-запросы отлично подходят для получения существующей информации и преобразования ее во что-то, что вы хотите. Но в вашем случае, если данные о дате действительно отсутствуют, то таблицы календаря — хороший способ ввести эти данные.   -  person Tim Biegeleisen    schedule 16.09.2016
comment
Фактически вы создаете список дат на лету, используя такую ​​технику: stackoverflow.com/questions/11141507/ в SQL Server. Эта таблица n может быть объединена с вашей таблицей по дате, чтобы получить количество (*) там, где это необходимо.   -  person zedfoxus    schedule 16.09.2016
comment
Каков ваш вклад?   -  person Kannan Kandasamy    schedule 16.09.2016
comment
Вы можете использовать рекурсивное CTE вместо таблицы дат.   -  person ajeh    schedule 16.09.2016
comment
Что ж, я пробую, но запрос уже занимает больше времени, чем следовало бы... Я продолжу играть с ним, и если никто не отправил фактический ответ, я опубликую, когда он заработает.   -  person JOATMON    schedule 16.09.2016
comment
Откат изменения тега, поскольку СУБД всегда актуальна...   -  person JOATMON    schedule 16.09.2016
comment
@KamilG.: имеет большое значение, какая СУБД используется (и если вы думаете, что это не так, вам не следовало добавлять тег sql-server)   -  person a_horse_with_no_name    schedule 16.09.2016
comment
Это была моя ошибка, исправлюсь, извините за сумбурность.   -  person Kamil Gosciminski    schedule 16.09.2016
comment
Caché не то, что вы бы назвали популярным, не беспокойтесь.   -  person JOATMON    schedule 16.09.2016
comment
Ну, я попробовал свои силы в OUTER JOIN с ограниченным везением. Пожалуйста, смотрите обновление   -  person JOATMON    schedule 16.09.2016


Ответы (2)


Итак, я перевернул агрегаты из редактирования в свой исходный пост, и теперь он работает:

Запрос

SELECT
        CAST(a.IndexedDate as varchar) as dt,
        COUNT(EventType) AS Logins
FROM 
        (
        SELECT DISTINCT(IndexedDate)
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        ) a
FULL OUTER JOIN (
        SELECT *
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        AND EventType = 'Login'
        ) b
ON 
        a.IndexedDate = b.IndexedDate
GROUP BY
        a.IndexedDate
ORDER BY
        a.IndexedDate DESC

Результаты

2016-09-13    41
2016-09-12    31
2016-09-11    0
2016-09-10    0
2016-09-09    15
2016-09-08    36

Обратите внимание, что мне пришлось заменить COUNT(*) на COUNT(EventType), чтобы он не учитывал дату из агрегата, который приводил к 1.

person JOATMON    schedule 16.09.2016
comment
Нет, 1 происходит из-за того, что когда вы full outer join выполняете b, каждая строка в b будет иметь строку в вашем наборе результатов, а это означает, что количество строк для этой даты действительно будет равно 1, даже если вы нет данных из таблицы a для этой строки. Вам нужно заменить выражение count(*) в select на case when a.IndexedDate is null then 0 else count(*) end, чтобы правильно назначить 0 для этих строк. - person SlimsGhost; 16.09.2016
comment
Я опередил тебя секунд на 10! Спасибо хоть! - person JOATMON; 16.09.2016
comment
Скотт, у вас есть событие для каждой IndexedDate, даже если это не тип события входа? Если бы у вас не было событий в данный день в вашей таблице, разве эта дата не была бы пропущена? - person EoinS; 16.09.2016
comment
Да, но, как упоминалось в исходном вопросе, я проверил, что все даты есть, плюс эта таблица МАССИВНА, и в день регистрируются десятки тысяч событий. Если дата отсутствует, значит, что-то не так :) - person JOATMON; 16.09.2016

Это работает (в SQL Server)

declare @mindt date = (select min(IndexedDate ) from p);
declare @dtrange int = DATEDIFF(day,@mindt,(select max(IndexedDate ) from p));

with MyCte AS
    (select   MyCounter = 0
     UNION ALL
     SELECT   MyCounter + 1
     FROM     MyCte
     where    MyCounter < @dtrange)
select coalesce(IndexedDate , dateadd(d, mycounter, @mindt)) IndexedDate
, count(IndexedDate)
from   MyCte 
left join p
  on dateadd(d,mycounter,@mindt) = p.IndexedDate 
group by coalesce(IndexedDate , dateadd(d, mycounter, @mindt))
option (maxrecursion 0);

В основном нам нужны две основные цифры: начальная дата и диапазон дат.

Мы создаем быстрый счетчик для количества дней в диапазоне дат.

Затем мы выбираем каждый слот в диапазоне дат и назначаем дату и значение, если их нет, мы создаем дату с DateAdd и назначаем 0 в качестве значения.

Вот функциональный пример

person EoinS    schedule 16.09.2016