如果没有可用于日期的行,则SQL Server分组行将返回默认值

时间:2018-04-07 07:19:20

标签: sql sql-server

我试图编写一个存储过程,根据月份对行进行分组,如果存在,则返回所有项目的总和,如果不存在则返回0。

对于查询的日期部分,我想要获得的是今天的日期 - 提取月份并返回5个月以收集任何数据(如果存在)。

在这个阶段,查询运行正常,但我想知道是否有任何方法可以优化它,因为看起来我一遍又一遍地运行同一组数据并且它在某种程度上也是硬编码的。

我想要实现的数据集如下:

Month    TotalAmount    TotalCount
-----------------------------------
2017-11  0              0
2017-12  200.00         2
2018-01  300.00         3
2018-02  0              0
2018-03  300.00         3
2018-04  100.00         1

使用下面的查询,我能够实现我想要的效果,但正如您所看到的,它在过去的5个月内难以编码,所以如果我想回到12个月,我会这样做必须添加更多代码。

DECLARE @5MonthAgo date = CAST(DATEADD(MONTH, -5, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -5, GETDATE())) AS DATE)
DECLARE @4MonthAgo date = CAST(DATEADD(MONTH, -4, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -4, GETDATE())) AS DATE)
DECLARE @3MonthAgo date = CAST(DATEADD(MONTH, -3, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -3, GETDATE())) AS DATE)
DECLARE @2MonthAgo date = CAST(DATEADD(MONTH, -2, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -2, GETDATE())) AS DATE)
DECLARE @1MonthAgo date = CAST(DATEADD(MONTH, -1, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -1, GETDATE())) AS DATE)
DECLARE @CurrentMonth date = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)

-- Table to return grouped and sum data
DECLARE @StatsTable TABLE ([Month] DATE, 
                           [Total Amount] DECIMAL(18,2),
                           [Total Count] INT
                          )

-- Temporary table to hold onto data batch - so table isn't used later on
DECLARE @TempGenTable TABLE ([Id] INT,
                             [Date] DATETIME,
                             [Lines] INT NULL,
                             [Amount] DECIMAL(18, 2) NULL
                            )

INSERT INTO @TempGenTable
    SELECT
        Id, Date, Lines, Amount
    FROM 
        TallyTable
    WHERE 
        Date >= @5MonthAgo

INSERT INTO @StatsTable
    SELECT
        @5MonthAgo,
        COALESCE((SELECT SUM(Amount)
                  FROM @TempGenTable
                  WHERE Date >= @5MonthAgo AND Date < @4MonthAgo
                  GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
        COALESCE((SELECT COUNT(Id)
                  FROM @TempGenTable
                  WHERE Date >= @5MonthAgo AND Date < @4MonthAgo
                  GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)

    UNION

    SELECT
        @4MonthAgo,
        COALESCE((SELECT SUM(Amount)
                  FROM @TempGenTable
                  WHERE Date >= @4MonthAgo AND Date < @3MonthAgo
                  GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
        COALESCE((SELECT COUNT(Id)
                  FROM @TempGenTable
                  WHERE Date >= @4MonthAgo AND Date < @3MonthAgo
                  GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
     ...

是否有更简单的方法可以在几个月内更灵活地获取上述数据?

最好让查询在一个月份变量中传递,它只检查当前月份,并在控制器中有一个循环返回x个月?

2 个答案:

答案 0 :(得分:0)

我会使用递归CTE生成数据,然后使用left join

with months as (
      select datefromparts(year(getdate()), month(getdate()), 1) as month_start, 5 as n
      union all
      select dateadd(month, -1, month_start), n - 1
      from months
      where n > 0
     )
select m.month_start, count(s.id), sum(s.amount)
from months m left join
     @StatsTable s
     on m.month_start = s.month
group by m.month_start
order by m.month_start;

您尚未提供样本数据,因此我不确定s.month的样子。您可能希望连接条件为:

on s.month >= m.month_start and s.month < dateadd(month, 1, m.month_start)

答案 1 :(得分:0)

以下是基于集合的方法,用于生成所需的月度期间:

--sample data
CREATE TABLE dbo.TallyTable (
      Id int
    , Date datetime
    , Lines int 
    , Amount decimal(18, 2)
    );
INSERT INTO dbo.TallyTable
VALUES
     (1, '2017-12-05', 1, 50.00)
    ,(2, '2017-12-06', 1, 150.00)
    ,(3, '2018-01-10', 1, 100.00)
    ,(4, '2018-01-11', 1, 100.00)
    ,(5, '2018-01-12', 1, 100.00)
    ,(6, '2018-03-15', 1, 225.00)
    ,(7, '2018-03-15', 1, 25.00)
    ,(8, '2018-03-15', 1, 50.00)
    ,(9, '2018-04-20', 1, 100.00);
GO

DECLARE @Months int = 5; --number of historical months
WITH
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t100 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b)
    , periods AS (SELECT
              CONVERT(varchar(7), DATEADD(month, DATEDIFF(month, '', GETDATE()) - num, ''),121) AS Month
            , DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num, '') AS PeriodStart
            , DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num + 1, '') AS NextPeriodStart
        FROM t100
        WHERE num <= @Months
    )
SELECT periods.Month, COALESCE(SUM(Amount), 0) AS TotalAmount, COALESCE(COUNT(ID), 0) AS TotalCount
FROM periods
LEFT JOIN dbo.TallyTable ON
    TallyTable.Date >= PeriodStart
    AND TallyTable.Date < NextPeriodStart
GROUP BY periods.Month
ORDER BY periods.Month;