如何计算T-SQL中的月数

时间:2017-01-10 07:29:03

标签: sql-server tsql

我在SQL Server中遇到了问题。

  

“Whate'er的设想得到了明确的说明,并且说它流畅的话语”,Nicolas Boileau-Despreaux

好吧,我不认为我能说清楚,但我会试试!而且我想为我糟糕的英语道歉!

我有这张桌子:

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

如果结果不是1,我想计算组合{ind,lvl}保留在结果1中的月份数,然后重新初始化月数为0。

显然,我需要得到类似的东西:

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

因此,如果我在查找 1 的结果为 1个且ID 1 >和lvl a ,我知道已经3个月了。

3 个答案:

答案 0 :(得分:2)

通过略微调整输入数据并略微调整我们定义需求的方式,生成预期结果变得非常简单。

首先,我们调整您的date值,以便唯一不同的是月份和年份 - 日期都是相同的。我已经选择这样做,我为每个值 1 添加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

1 执行调整的另一种方法是使用DATEADD / DATEDIFF对执行" floor"对日期的操作:

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

将所有日期值重置为本月的第一个而不是下个月。这可能会更多地自然而且#34;对您而言,或者您可能已经在原始数据中提供了此类值。

答案 1 :(得分: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

答案 2 :(得分: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;