2016-12-15 51 views
2

我有点挣扎于找到一个干净的方式来做到这一点。假设我在我的桌子下面的记录命名Records与群组和加入的累积和

|Name| |InsertDate| |Size| 
    john 30.06.2015  1 
    john 10.01.2016  10 
    john 12.01.2016  100 
    john 05.03.2016  1000 
    doe  01.01.2016  1 

如何获得记录的2016年月等于或小于3按月份进行分组(甚至一个月不存在如在这种情况下的第2个月),包括该月的累计总和为Size?我想要得到的结果如下所示:

|Name| |Month| |Size| 
    john  1  111 
    john  2  111 
    john  3  1111 
    doe  1  1 
+0

你需要,你可以使用外加入日历表。请参见[this](https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/)或[this](https:// sqlperformance的.com/2013/01/T-SQL查询/生成-A-设置-3)。 –

+0

其实我想提供一些静态值(几个月)作为左外连接表,但无法找到它的方式。 – sotn

+0

什么是静态值?另请注意,如果您的客户在过去的2年中要求您提供查询,那么您的查询应该可以正常工作,因此您需要将其变为动态并包含年份。一旦你有日历表,这并不难。您只需按年份+月份进行分组,并通过子查询选择“MIN(InsertDate)'和'MAX(InsertDate)'来获取开始日期和结束日期。 –

回答

1

正如其他评论者已经指出的那样,你只需要在与日期的表,你可以join给你,你的源表没有日期有记载:

-- Build the source data table. 
declare @t table(Name nvarchar(10) 
       ,InsertDate date 
       ,Size int 
       ); 
insert into @t values 
('john','20150630',1 ) 
,('john','20160110',10 ) 
,('john','20160112',100) 
,('john','20160305',1000) 
,('doe' ,'20160101',1 ); 

-- Specify the year you want to search for by storing the first day here. 
declare @year date = '20160101'; 

-- This derived table builds a set of dates that you can join from. 
-- LEFT JOINing from here is what gives you rows for months without records in your source data. 
with Dates 
as 
(
    select @year as MonthStart 
      ,dateadd(day,-1,dateadd(month,1,@year)) as MonthEnd 
    union all 
    select dateadd(month,1,MonthStart) 
      ,dateadd(day,-1,dateadd(month,2,MonthStart)) 
    from Dates 
    where dateadd(month,1,MonthStart) < dateadd(yyyy,1,@year) 
) 
select t.Name 
     ,d.MonthStart 
     ,sum(t.Size) as Size 
from Dates d 
    left join @t t 
     on(t.InsertDate <= d.MonthEnd) 
where d.MonthStart <= '20160301'  -- Without knowing what your logic is for specifying values only up to March, I have left this part for you to automate. 
group by t.Name 
     ,d.MonthStart 
order by t.Name 
     ,d.MonthStart; 

如果你在你的数据库中的静态日期的引用表,你不需要做派生表的创建和可以这样做:

select d.DateValue 
     ,<Other columns> 
from DatesReferenceTable d 
    left join <Other Tables> o 
     on(d.DateValue = o.AnyDateColumn) 
etc 
+0

你忘了在末尾加上GROUP BY: GROUP BY t.name,d.MonthStart –

+0

Doh !!!!我的错。我的手机上的代码块没有显示滚动条。对于那个很抱歉。 –

+0

感谢您的回答,我最终因为某些公司规则而使用了其他方法(例如,查询必须以'select'子句开始等等,并且'with'子句不可能用于我,我知道我很傻,但我们的DBA赢了不赞成..)这将适用于大多数用户,虽然.. – sotn

1

这是另一种利用计数表(又名数字表)创建日期表的方法。注意我的意见。

-- Build the source data table. 
declare @t table(Name nvarchar(10), InsertDate date, Size int); 
insert into @t values 
('john','20150630',1 ) 
,('john','20160110',10 ) 
,('john','20160112',100) 
,('john','20160305',1000) 
,('doe' ,'20160101',1 ); 

-- A year is fine, don't need a date data type 
declare @year smallint = 2016; 

WITH -- dummy rows for a tally table: 
E AS (SELECT E FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(e)), 
dateRange(totalDays, mn, mx) AS -- Get the range and number of months to create 
(
    SELECT DATEDIFF(MONTH, MIN(InsertDate), MAX(InsertDate)), MIN(InsertDate), MAX(InsertDate) 
    FROM @t 
), 
iTally(N) AS -- Tally Oh! Create an inline Tally (aka numbers) table starting with 0 
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 
    FROM E a CROSS JOIN E b CROSS JOIN E c CROSS JOIN E d 
), 
RunningTotal AS -- perform a running total by year/month for each person (Name) 
(
    SELECT 
    yr = YEAR(DATEADD(MONTH, n, mn)), 
    mo = MONTH(DATEADD(MONTH, n, mn)), 
    Name, 
    Size = SUM(Size) OVER 
     (PARTITION BY Name ORDER BY YEAR(DATEADD(MONTH, n, mn)), MONTH(DATEADD(MONTH, n, mn))) 
    FROM iTally 
    CROSS JOIN dateRange 
    LEFT JOIN @t ON MONTH(InsertDate) = MONTH(DATEADD(MONTH, n, mn)) 
    WHERE N <= totalDays 
) -- Final output will only return rows where the year matches @year: 
SELECT 
    name = ISNULL(name, LAG(Name, 1) OVER (ORDER BY yr, mo)), 
    yr, mo, 
    size = ISNULL(Size, LAG(Size, 1) OVER (ORDER BY yr, mo)) 
FROM RunningTotal 
WHERE yr = @year 
GROUP BY yr, mo, name, size; 

结果:

name  yr   mo   size 
---------- ----------- ----------- ----------- 
doe  2016  1   1 
john  2016  1   111 
john  2016  2   111 
john  2016  3   1111 
+0

感谢您的答案。理货表的使用很好。 – sotn

+0

请注意,在“连接”和“分区”中使用'month'和'year'等函数会阻止使用索引,因为需要计算每个连接值。 – iamdave

+0

@iamdave - 这就是为什么我在正确的谷物上设计桌子很好。如果按月份和年份加入或分组,则应该有月份和年份列,并且应该正确编制索引。 –