Включить отсутствующие месяцы в группе по запросу

Я думаю, что у меня здесь тяжелый...: (

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

SELECT datename(month, OrderDate) as Month, COUNT(OrderNumber) AS Orders
FROM OrderTable
WHERE OrderDate >= '2012-01-01' and OrderDate <= '2012-06-30'
GROUP BY year(OrderDate), month(OrderDate), datename(month, OrderDate)

Что я хочу получить, это примерно так:

Month            Orders
-----            ------
January          10
February         7
March            0
April            12
May              0
June             5

... но мой запрос пропускает строку для марта и мая. Я пробовал COALESCE(COUNT(OrderNumber), 0) и ISNULL(COUNT(OrderNumber), 0), но я уверен, что группировка не позволяет работать.

Ответ 1

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

DECLARE @StartDate SMALLDATETIME, @EndDate SMALLDATETIME;

SELECT @StartDate = '20120101', @EndDate = '20120630';

;WITH d(d) AS 
(
  SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, @StartDate), 0))
  FROM ( SELECT TOP (DATEDIFF(MONTH, @StartDate, @EndDate) + 1) 
    n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
    FROM sys.all_objects ORDER BY [object_id] ) AS n
)
SELECT 
  [Month]    = DATENAME(MONTH, d.d), 
  [Year]     = YEAR(d.d), 
  OrderCount = COUNT(o.OrderNumber) 
FROM d LEFT OUTER JOIN dbo.OrderTable AS o
  ON o.OrderDate >= d.d
  AND o.OrderDate < DATEADD(MONTH, 1, d.d)
GROUP BY d.d
ORDER BY d.d;

Ответ 2

Поскольку ваш запрос Just Can not угадает месяцы, которые вы хотите, вам нужно будет иметь месяцы, которые вы хотите сохранить где-то, соедините их со своей таблицей и затем сгруппируйте. Что-то вроде:

;With Months (Month) 
AS
(

    select 'January' as Month
    UNION
    select 'February' as Month
    UNION
    select 'March' as Month
    UNION
    select 'April' as Month
    UNION
    select 'May' as Month
    UNION
    select 'June' as Month
    UNION
    select 'July' as Month
    UNION
    select 'August' as Month
    UNION
    select 'September' as Month
    UNION
    select 'October' as Month
    UNION
    select 'November' as Month
    UNION
    select 'December' as Month

)
--Also you could have them in a "Months" Table

Затем просто JOIN эту таблицу со своей таблицей:

   Select 
    SELECT datename(month, OrderDate) as Month, COUNT(OrderNumber) 
    FROM Months T1
    LEFT JOIN OrderTable T2 on datename(month, T2.OrderDate) = T2.Month
    WHERE (T2.OrderDate >= '2012-01-01' and T2.OrderDate <= '2012-06-30') 
OR T2.OrderDate IS NULL --So will show you the months with no rows
    GROUP BY year(T2.OrderDate), month(T2.OrderDate), datename(month, T2.OrderDate)

Надеюсь, что это сработает!

Ответ 3

Здесь используется рекурсивный CTE:

declare @StartDate datetime = '2015-04-01';
declare @EndDate datetime = '2015-06-01';

-- sample data
declare @orders table (OrderNumber int, OrderDate datetime);
insert into @orders
select 11, '2015-04-02'
union all
select 12, '2015-04-03'
union all
select 13, '2015-05-03'
;

-- recursive CTE
with dates
as (
    select @StartDate as reportMonth
    union all
    select dateadd(m, 1, reportMonth)
    from dates
    where reportMonth < @EndDate
    )
select 
    reportMonth,
    Count = count(o.OrderNumber)
from dates
left outer join @orders as o 
    on o.OrderDate >= reportMonth
    and o.OrderDate < dateadd(MONTH, 1, reportMonth)
group by 
    reportMonth
option (maxrecursion 0);
;