2017-01-10 114 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