2016-12-15 50 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 - 這就是爲什麼我在正確的穀物上設計桌子很好。如果按月份和年份加入或分組,則應該有月份和年份列,並且應該正確編制索引。 –