2017-01-10 87 views
1

我在SQL Server中遇到了問題。如何統計T-SQL中的月數

「Whate'er是精心構思顯然是說,而且說的話也很容易流」,尼古拉·布瓦洛

嗯,我不認爲我能說清楚,但我會嘗試!我想爲我的壞英語道歉!

我有這個表:

id ind lvl result date 
1 1 a 3 2017-01-31 
2 1 a 3 2017-02-28 
3 1 a 1 2017-03-31 
4 1 a 1 2017-04-30 
5 1 a 1 2017-05-31 
6 1 b 1 2017-01-31 
7 1 b 3 2017-02-28 
8 1 b 3 2017-03-31 
9 1 b 1 2017-04-30 
10 1 b 1 2017-05-31 
11 2 a 3 2017-01-31 
12 2 a 1 2017-02-28 
13 2 a 3 2017-03-31 
14 2 a 1 2017-04-30 
15 2 a 3 2017-05-31 

我想指望月份的數字組合{IND,LVL}留在結果1重新初始化月份的數字爲0,前如果結果不是1

很顯然,我需要得到類似的東西:

id ind lvl result date BadResultRemainsFor%Months 
1 1 a 3 2017-01-31 0 
2 1 a 3 2017-02-28 0 
3 1 a 1 2017-03-31 1 
4 1 a 1 2017-04-30 2 
5 1 a 1 2017-05-31 3 
6 1 b 1 2017-01-31 1 
7 1 b 3 2017-02-28 0 
8 1 b 3 2017-03-31 0 
9 1 b 1 2017-04-30 1 
10 1 b 1 2017-05-31 2 
11 2 a 3 2017-01-31 0 
12 2 a 1 2017-02-28 1 
13 2 a 3 2017-03-31 0 
14 2 a 1 2017-04-30 1 
15 2 a 3 2017-05-31 0 

所以,如果我一直在尋找的月數的結果是的日期2017-05-31與編號和lvl a,我知道它已經3個月。

+2

難道我們保證所有'日期'值都是日期,並且每個月的最後一天都是? –

+0

@Damien_The_Unbeliever是的,它將永遠是每個月的最後一天! – Flesym

回答

2

假設所有日期的月份的最後一天:

;WITH tb(id,ind,lvl,result,date) AS(
     select 1,1,'a',3,'2017-01-31' UNION 
     select 2,1,'a',3,'2017-02-28' UNION 
     select 3,1,'a',1,'2017-03-31' UNION 
     select 4,1,'a',1,'2017-04-30' UNION 
     select 5,1,'a',1,'2017-05-31' UNION 
     select 6,1,'b',1,'2017-01-31' UNION 
     select 7,1,'b',3,'2017-02-28' UNION 
     select 8,1,'b',3,'2017-03-31' UNION 
     select 9,1,'b',1,'2017-04-30' UNION 
     select 10,1,'b',1,'2017-05-31' UNION 
     select 11,2,'a',3,'2017-01-31' UNION 
     select 12,2,'a',1,'2017-02-28' UNION 
     select 13,2,'a',3,'2017-03-31' UNION 
     select 14,2,'a',1,'2017-04-30' UNION 
     select 15,2,'a',3,'2017-05-31' 
    ) 
    SELECT t.id,t.ind,t.lvl,t.result,t.date 
    ,CASE WHEN t.isMatched=1 THEN ROW_NUMBER()OVER(PARTITION BY t.ind,t.lvl,t.id-t.rn ORDER BY t.id) ELSE 0 END 
    FROM (
     SELECT t1.*,c.MonthDiff,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END AS isMatched 
       ,CASE WHEN c.MonthDiff=t1.result THEN ROW_NUMBER()OVER(PARTITION BY t1.ind,t1.lvl,CASE WHEN c.MonthDiff=t1.result THEN 1 ELSE 0 END ORDER BY t1.id) ELSE null END AS rn 
     FROM tb AS t1 
     LEFT JOIN tb AS t2 ON t1.ind=t2.ind AND t1.lvl=t2.lvl AND t2.id=t1.id-1 
     CROSS APPLY(VALUES(ISNULL(DATEDIFF(MONTH,t2.date,t1.date),1))) c(MonthDiff) 

    ) AS t 
    ORDER BY t.id 
 
id   ind   lvl result  date  
----------- ----------- ---- ----------- ---------- -------------------- 
1   1   a 3   2017-01-31 0 
2   1   a 3   2017-02-28 0 
3   1   a 1   2017-03-31 1 
4   1   a 1   2017-04-30 2 
5   1   a 1   2017-05-31 3 
6   1   b 1   2017-01-31 1 
7   1   b 3   2017-02-28 0 
8   1   b 3   2017-03-31 0 
9   1   b 1   2017-04-30 1 
10   1   b 1   2017-05-31 2 
11   2   a 3   2017-01-31 0 
12   2   a 1   2017-02-28 1 
13   2   a 3   2017-03-31 0 
14   2   a 1   2017-04-30 1 
15   2   a 3   2017-05-31 0 

+0

感謝Nolan Shang爲您的答案! – Flesym

2

通過稍微調整輸入數據並略微調整我們如何定義需求,生成預期結果變得非常簡單。

首先,我們調整您的date值,以便唯一變化的是月和年 - 日子都是一樣的。我選擇這樣做,我爲每個值添加1天。事實上,這產生了一個月提前的結果在這裏並不重要,因爲所有的價值都是相似的變化,所以每月的關係保持不變。然後,我們介紹一個數字表 - 在這裏,我假設一個小的固定表就足夠了。如果它不適合您的需求,您可以輕鬆地在線查找示例以創建可用於此查詢的大型固定數字表。

最後,我們重鑄問題陳述。我們不是試圖計算幾個月,而是問「什麼是最小月數,大於等於零,我需要從當前行返回,找到一個非1結果的行?」。因此,我們產生這樣的查詢:

declare @t table (id int not null,ind int not null,lvl varchar(13) not null, 
result int not null,date date not null) 
insert into @t(id,ind,lvl,result,date) values 
(1 ,1,'a',3,'20170131'), (2 ,1,'a',3,'20170228'), (3 ,1,'a',1,'20170331'), 
(4 ,1,'a',1,'20170430'), (5 ,1,'a',1,'20170531'), (6 ,1,'b',1,'20170131'), 
(7 ,1,'b',3,'20170228'), (8 ,1,'b',3,'20170331'), (9 ,1,'b',1,'20170430'), 
(10,1,'b',1,'20170531'), (11,2,'a',3,'20170131'), (12,2,'a',1,'20170228'), 
(13,2,'a',3,'20170331'), (14,2,'a',1,'20170430'), (15,2,'a',3,'20170531') 

;With Tweaked as (
    select 
     *, 
     DATEADD(day,1,date) as dp1d 
    from 
     @t 
), Numbers(n) as (
    select 0 union all select 1 union all select 2 union all select 3 union all select 4 
    union all 
    select 5 union all select 6 union all select 7 union all select 8 union all select 9 
) 
select 
    id,  ind,  lvl,  result,  date, 
    COALESCE(
     (select MIN(n) from Numbers n1 
     inner join Tweaked t2 
     on 
      t2.ind = t1.ind and 
      t2.lvl = t1.lvl and 
      t2.dp1d = DATEADD(month,-n,t1.dp1d) 
     where 
      t2.result != 1 
     ), 
     1) as [BadResultRemainsFor%Months] 
from 
    Tweaked t1 

COALESCE只是有對付邊緣的情況下,如您1,b數據,在沒有前一行與非1的結果。

結果:

id   ind   lvl   result  date  BadResultRemainsFor%Months 
----------- ----------- ------------- ----------- ---------- -------------------------- 
1   1   a    3   2017-01-31 0 
2   1   a    3   2017-02-28 0 
3   1   a    1   2017-03-31 1 
4   1   a    1   2017-04-30 2 
5   1   a    1   2017-05-31 3 
6   1   b    1   2017-01-31 1 
7   1   b    3   2017-02-28 0 
8   1   b    3   2017-03-31 0 
9   1   b    1   2017-04-30 1 
10   1   b    1   2017-05-31 2 
11   2   a    3   2017-01-31 0 
12   2   a    1   2017-02-28 1 
13   2   a    3   2017-03-31 0 
14   2   a    1   2017-04-30 1 
15   2   a    3   2017-05-31 0 

進行調整的另一種方法是使用一個DATEADD/DATEDIFF對到對的日期執行 「地板」 操作:

DATEADD(month,DATEDIFF(month,0,date),0) as dp1d 

它將所有日期值重置爲本月的第一個月,而不是下一個月。這對你來說可能更加「自然」,或者你的原始數據中可能已經有了這樣的值。

+0

感謝Damien_The_Unbeliever爲您的答案! – Flesym

1

假設日期在本月的汽車無增加,您可以使用窗口函數像這樣:

select 
    t.id, ind, lvl, result, dat, 
    case when result = 1 then row_number() over (partition by grp order by id) else 0 end x 
from (
select t.*, 
    dense_rank() over (order by e, result) grp 
from (
select 
    t.*, 
    row_number() over (order by id) - row_number() over (partition by ind, lvl, result order by id) e 
from your_table t 
order by id) t) t; 
+0

感謝GurVfor你的回答! – Flesym