Выберите дату и количество, сгруппируйте по дате — Как отображать даты с нулевым количеством отсчетов?

#tsql #subquery #distinct #aggregate-functions

#tsql #подзапрос #distinct #агрегатные функции

Вопрос:

 SELECT
    CAST(c.DT AS DATE) AS 'Date'
    , COUNT(p.PatternID) AS 'Count'
FROM CalendarMain c
LEFT OUTER JOIN Pattern p
    ON c.DT = p.PatternDate
INNER JOIN Result r
    ON p.PatternID = r.PatternID
INNER JOIN Detail d
    ON p.PatternID = d.PatternID
WHERE r.Type = 7
AND d.Panel = 501
AND CAST(c.DT AS DATE) 
    BETWEEN '20190101' AND '20190201'
GROUP BY CAST(c.DT AS DATE)
ORDER BY CAST(c.DT AS DATE)
  

Приведенный выше запрос у меня не работает. Он по-прежнему пропускает дни, когда количество равно нулю, для его c.DT.

c.DT и p.PatternDate оба являются временем DateTime, хотя c.DT не может быть нулевым. На самом деле это PK для таблицы. Он заполняется как DateTimes для каждого отдельного дня с 2015 по 2049 год, поэтому записи за эти дни существуют.

Еще одна странная вещь, которую я заметил, это то, что при присоединении к C.DT = p.PatternDate вообще ничего не возвращается без приведения или ПРЕОБРАЗОВАНИЯ в стиль даты. Не уверен, почему, когда они оба являются DateTimes.

Ответ №1:

Здесь есть о чем поговорить. На данном этапе неясно, что вы на самом деле пытаетесь посчитать. Если это количество «шаблонов» в день за январь 2019 года, то:

  • Ваш BETWEEN также будет учитывать любую активность, происходящую 1 февраля,
  • Похоже, что один шаблон может иметь несколько результатов, что потенциально приводит к ошибочному подсчету
  • Похоже, что один шаблон может содержать несколько деталей, что потенциально может привести к ошибочному подсчету
  • Если в одном шаблоне есть, скажем, 3 подходящих результата, а также 4 детали, вы получите их перекрестный продукт. Ваше количество будет равно 12.

Я собираюсь предположить:

  • вам нужно только определенное количество шаблонов, независимо от количества деталей и результатов.
  • Вам нужна только активность за январь

--Set up some dummy data

 DROP TABLE IF EXISTS #CalendarMain
SELECT cast('20190101' as datetime) as DT
INTO #CalendarMain
UNION ALL SELECT '20190102' as DT
UNION ALL SELECT '20190103' as DT
UNION ALL SELECT '20190104' as DT
UNION ALL SELECT '20190105' as DT  --etc etc
;

     DROP TABLE IF EXISTS #Pattern
   SELECT cast('1'as int)                         as PatternID
          ,cast('20190101 13:00' as datetime)     as PatternDate
     INTO #Pattern
UNION ALL SELECT 2,'20190101 14:00'
UNION ALL SELECT 3,'20190101 15:00'
UNION ALL SELECT 4,'20190104 11:00'
UNION ALL SELECT 5,'20190104 14:00'
;

     DROP TABLE IF EXISTS #Result
   SELECT cast(100 as int)                as ResultID
          ,cast(1 as int)                 as PatternID
          ,cast(7 as int)                 as [Type]
     INTO #Result
UNION ALL SELECT 101,1,7
UNION ALL SELECT 102,1,8
UNION ALL SELECT 103,1,9
UNION ALL SELECT 104,2,8
UNION ALL SELECT 105,2,7
UNION ALL SELECT 106,3,7
UNION ALL SELECT 107,3,8
UNION ALL SELECT 108,4,7
UNION ALL SELECT 109,5,7
UNION ALL SELECT 110,5,8
;

     DROP TABLE IF EXISTS #Detail
   SELECT cast(201 as int)                as DetailID
          ,cast(1 as int)                 as PatternID
          ,cast(501 as int)               as Panel
     INTO #Detail
UNION ALL SELECT 202,1,502
UNION ALL SELECT 203,1,503
UNION ALL SELECT 204,1,502
UNION ALL SELECT 205,1,502
UNION ALL SELECT 206,1,502
UNION ALL SELECT 207,2,502
UNION ALL SELECT 208,2,503
UNION ALL SELECT 209,2,502
UNION ALL SELECT 210,4,502
UNION ALL SELECT 211,4,501
;
       -- create some variables 
  DECLARE @start_date as date = '20190101';
  DECLARE @end_date   as date = '20190201'; --I assume this is an exclusive end date

   SELECT cal.DT
          ,isnull(patterns.[count],0) as [Count]
     FROM #CalendarMain cal
          LEFT JOIN (   SELECT cast(p.PatternDate as date)  as PatternDate
                               ,COUNT(DISTINCT p.PatternID) as [Count]
                          FROM #Pattern     p
                               JOIN #Result r ON p.PatternID = r.PatternID
                               JOIN #Detail d ON p.PatternID = d.PatternID
                         WHERE r.[Type] = 7
                               and d.Panel = 501
                      GROUP BY cast(p.PatternDate as date)
                    ) patterns ON cal.DT = patterns.patternDate
    WHERE cal.DT >= @start_date
          and cal.DT < @end_date  --Your code would have included 1 Feb, which I assume was unintentional.
 ORDER BY cal.DT
;