【问题标题】:subtract total counts from column in the previous row using SQL使用 SQL 从上一行中的列中减去总计数
【发布时间】:2018-10-17 04:27:51
【问题描述】:

我有以下代码:

create table #attr( enroll_month datetime ,cncl_mth datetime,
mth int,
tot_orders int,tot_cancel int, active_count int, attr_rate int , retn_rate 
int
)

DECLARE
@enroll_mth datetime ,@cncl_mth datetime, @mth int ,
@tot_orders numeric, @tot_cancel numeric,
@attr_rate numeric(6,2), @retn_rate numeric(6,2),
@active_count int
DECLARE att_cursor CURSOR FOR
SELECT
d.Enroll_Month, d.cncl_mth, d.mth,
s.tot_orders, d.tot_cancel
FROM #Summary s with (nolock),
#Detail d with (nolock)
WHERE
s.Enroll_Month = d.Enroll_Month


OPEN att_cursor
FETCH NEXT FROM att_cursor INTO @enroll_mth, @cncl_mth, @mth, @tot_orders, 
@tot_cancel

DECLARE
@old_enroll_mth datetime,
@old_cncl_mth datetime, @old_mth int, @month datetime ,
@intial varchar(1),
@old_active_cnt int, @old_tot_cancel int,
@old_retn_rate numeric(6,2), @old_attr_rate numeric(6,2),
@counter int

SELECT @old_enroll_mth = ''
SELECT @intial = 'Y'

WHILE @@FETCH_STATUS = 0
BEGIN
IF (@old_enroll_mth <> @enroll_mth)
BEGIN
SELECT @active_count = @active_count - @tot_cancel
SELECT @intial = 'N'
END
ELSE
BEGIN
SELECT @active_count = @tot_orders - @tot_cancel
SELECT @intial = 'Y'
END

SELECT @retn_rate = (@active_count / @tot_orders) * 100
SELECT @attr_rate = 100 - @retn_rate

INSERT INTO #Attr (
 enroll_month, cncl_mth, mth, tot_orders, tot_cancel, 
active_count, attr_rate, retn_rate ) 
VALUES (
@enroll_mth, @cncl_mth, @mth, @tot_orders, @tot_cancel,
@active_count, @attr_rate, @retn_rate)





SELECT @old_enroll_mth = @enroll_mth
SELECT @old_mth = @mth
SELECT @old_retn_rate = @retn_rate
SELECT @old_attr_rate = @attr_rate
SELECT @old_active_cnt = @active_count
SELECT @old_cncl_mth = @cncl_mth
SELECT @old_tot_cancel = @tot_cancel

FETCH NEXT FROM att_cursor INTO @enroll_mth, @cncl_mth, @mth, @tot_orders, 
@tot_cancel
END

CLOSE att_cursor
DEALLOCATE att_cursor

select * from #attr

返回以下输出。

enroll_month cncl_mth mth tot_orders tot_cancel active_count attr_rate retn_rate
01/01/17    01/01/17    1   390         160        230           41       58
01/01/17   02/01/17     2   390          26        364            6       93
01/01/17    03/01/17    3   390          23         594          -52      152

它正在显示 mth=1 的活动计数的正确值。虽然对于 mth= 2 它应该是 (230-26=204) 而对于 mth = 3 它应该是 (204-23 =181)。

我已经发布了两个脚本来填充上面脚本中使用的摘要和详细信息表。

create table #summary
(
enroll_month datetime,
tot_orders int
)
go
insert into #summary(enroll_month, tot_orders)
values ('2017-01-01 00:00:00.000', 390)

insert into #summary(enroll_month, tot_orders )
values ('2017-02-01 00:00:00.000', 615)
go

drop table #Detail
go
create table #detail
(
enroll_month datetime,
cncl_mnth datetime,
mth int,
tot_cancel int
)
go
insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel)
values ('2017-01-01 00:00:00.000', '2017-01-01 00:00:00.000', 1, 160)

insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel )
values ('2017-01-01 00:00:00.000','2017-02-01 00:00:00.000', 2, 26)

insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel)
values ('2017-01-01 00:00:00.000','2017-03-01 00:00:00.000', 3, 23)
go

你能帮忙吗?

谢谢, 帕姆

【问题讨论】:

  • 你能发布查询的执行计划吗(我已经在我的回答中发布了一个例子)?您使用的是什么版本的 SQL Server?两个表中的 [enroll_month] 列上是否有索引?您能否发布更多示例记录,以便我可以根据模式构建 250 条记录?

标签: sql-server tsql


【解决方案1】:

这有用吗?

    create table #summary ( enroll_month datetime, tot_orders int ) 
    insert into #summary(enroll_month, tot_orders) values ('2017-01-01 00:00:00.000', 390)
    insert into #summary(enroll_month, tot_orders ) values ('2017-02-01 00:00:00.000', 615) 


    create table #detail ( enroll_month datetime, cncl_mnth datetime, mth int, tot_cancel int ) 
    insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel) values ('2017-01-01 00:00:00.000', '2017-01-01 00:00:00.000', 1, 160)
    insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel ) values ('2017-01-01 00:00:00.000','2017-02-01 00:00:00.000', 2, 26)
    insert into #detail(enroll_month,cncl_mnth,mth,tot_cancel) values ('2017-01-01 00:00:00.000','2017-03-01 00:00:00.000', 3, 23)



    SELECT
    d.Enroll_Month, d.cncl_mnth, d.mth,
    s.tot_orders, d.tot_cancel
    into #tmp
    FROM #Summary s with (nolock),
    #Detail d with (nolock)
    WHERE
    s.Enroll_Month = d.Enroll_Month

    ;with cte
    As
    (
        Select Enroll_Month,cncl_mnth,mth,tot_orders,tot_cancel,
        tot_orders-tot_cancel as active_count,
        FLOOR(100-(CAST((tot_orders-tot_cancel) AS FLOAT)/CAST(tot_orders AS FLOAT))*100) as attr_rate, 
        FLOOR((CAST((tot_orders-tot_cancel) AS FLOAT)/CAST(tot_orders AS FLOAT))*100) as retn_rate
        from #tmp where mth=1

        Union All

        Select t.Enroll_Month,t.cncl_mnth,t.mth,t.tot_orders,t.tot_cancel,
        c.active_count-t.tot_cancel as active_count,
        FLOOR(100-(CAST((c.active_count-t.tot_cancel) AS FLOAT)/CAST(c.active_count AS FLOAT))*100) as attr_rate,   
        FLOOR((CAST((c.active_count-t.tot_cancel) AS FLOAT)/CAST(c.active_count AS FLOAT))*100) as retn_rate
        from #tmp t
        JOIN cte c on t.mth=c.mth+1
    )

    select * from cte

    Drop Table #tmp
    Drop table #summary
    Drop table #detail

输出是:

Enroll_Month                    cncl_mnth          mth  tot_orders  tot_cancel  active_count    attr_rate   retn_rate
2017-01-01 00:00:00.000  2017-01-01 00:00:00.000    1      390         160         230               41          58
2017-01-01 00:00:00.000  2017-02-01 00:00:00.000    2      390         26          204               11          88
2017-01-01 00:00:00.000  2017-03-01 00:00:00.000    3      390         23          181               11          88

【讨论】:

  • 感谢 Sahi 提供的解决方案。我正在使用包含 250 行的 DataDetail 表,并且具有与代码中使用的 #detail 表相同的数据元素和结构。当我用 Datadetail 表替换 #detail 表时,查询会运行很长时间。我不得不停止它,因为它运行了 30 分钟。有没有办法可以优化。谢谢! ——
【解决方案2】:

我会使用common table expression (CTE) 并使用SUMOVER 子句生成总数。

SQL:

WITH
summary
AS
(
    SELECT tbl.* FROM (VALUES
      ( '01-Jan-2017', 390)
    , ( '01-Feb-2017', 615)
    ) tbl ([enroll_month], [tot_orders]) 
)
, 
details
AS
(
    SELECT tbl.* FROM (VALUES
      ( '01-Jan-2017', '01-Jan-2017', 1, 160)
    , ( '01-Jan-2017', '01-Feb-2017', 2, 26)
    , ( '01-Jan-2017', '01-Mar-2017', 3, 23)
    ) tbl ([enroll_month], [cncl_mnth], [mth], [tot_cancel]) 
)
,
detail_active_count
AS
(
    SELECT
          d.[enroll_month]
        , d.[cncl_mnth]
        , s.[tot_orders]
        , d.[mth]
        , d.[tot_cancel]
        , [active_count] = s.[tot_orders] - SUM(d.[tot_cancel]) OVER (PARTITION BY d.[enroll_month] ORDER BY d.[mth]) 
    FROM 
        summary AS s
        INNER JOIN details as d ON d.[enroll_month] = s.[enroll_month]
)
SELECT
      c.* 
    , [attr_rate] = FLOOR(100 - (CAST((c.[active_count]) AS FLOAT) / CAST(c.[tot_orders] AS FLOAT)) * 100)
    , [retn_rate] = FLOOR((CAST((c.[active_count]) AS FLOAT) / CAST(c.[tot_orders] AS FLOAT)) * 100)
FROM 
    detail_active_count AS c

结果:

执行计划示例:

【讨论】:

  • 感谢您的解决方案!我正在使用包含 250 行的 DataDetail 表,并且具有与代码中使用的 #detail 表相同的数据元素和结构。当我用 Datadetail 表替换 #detail 表时,查询会运行很长时间。我不得不停止它,因为它运行了 30 分钟。有没有办法可以优化。谢谢!
【解决方案3】:

你的问题在这部分:

IF (@old_enroll_mth <> @enroll_mth)
BEGIN
SELECT @active_count = @active_count - @tot_cancel
SELECT @intial = 'N'
END
ELSE
BEGIN
SELECT @active_count = @tot_orders - @tot_cancel
SELECT @intial = 'Y'
END

你已经颠覆了你需要的逻辑;).. 所以你需要它是这样的:

IF (@old_enroll_mth <> @enroll_mth)
BEGIN
    SELECT @active_count = @tot_orders - @tot_cancel
    SELECT @intial = 'Y'
END
ELSE
BEGIN
    SELECT @active_count = @active_count - @tot_cancel
    SELECT @intial = 'N'
END

更新

样本数据

DECLARE @summary TABLE (enroll_month datetime,  tot_orders int)
DECLARE @detail TABLE (enroll_month datetime, cncl_mnth datetime, mth int, tot_cancel int)
DECLARE @attr TABLE (enroll_month datetime ,cncl_mth datetime, mth int, tot_orders int,tot_cancel int, active_count int, attr_rate int , retn_rate int)

insert into @summary(enroll_month, tot_orders) values 
('2017-01-01 00:00:00.000', 390),
('2017-02-01 00:00:00.000', 615)

insert into @detail(enroll_month,cncl_mnth,mth,tot_cancel) values 
('2017-01-01 00:00:00.000', '2017-01-01 00:00:00.000', 1, 160),
('2017-01-01 00:00:00.000','2017-02-01 00:00:00.000', 2, 26),
('2017-01-01 00:00:00.000','2017-03-01 00:00:00.000', 3, 23)

使用上面的示例和你的CURSOR(修改上面的逻辑 - 翻转):

DECLARE
@enroll_mth datetime ,@cncl_mth datetime, @mth int ,
@tot_orders numeric, @tot_cancel numeric,
@attr_rate numeric(6,2), @retn_rate numeric(6,2),
@active_count int


DECLARE att_cursor CURSOR FOR
    SELECT
    d.Enroll_Month, d.cncl_mnth, d.mth,
    s.tot_orders, d.tot_cancel
    FROM @summary s, @Detail d
    WHERE
    s.Enroll_Month = d.Enroll_Month


OPEN att_cursor
FETCH NEXT FROM att_cursor INTO @enroll_mth, @cncl_mth, @mth, @tot_orders, @tot_cancel

    DECLARE
    @old_enroll_mth datetime,
    @old_cncl_mth datetime, @old_mth int, @month datetime ,
    @intial varchar(1),
    @old_active_cnt int, @old_tot_cancel int,
    @old_retn_rate numeric(6,2), @old_attr_rate numeric(6,2),
    @counter int

SELECT @old_enroll_mth = ''
SELECT @intial = 'Y'

WHILE @@FETCH_STATUS = 0
BEGIN
IF (@old_enroll_mth <> @enroll_mth)
BEGIN
    SELECT @active_count = @tot_orders - @tot_cancel
    SELECT @intial = 'Y'
END
ELSE
BEGIN
    SELECT @active_count = @active_count - @tot_cancel
    SELECT @intial = 'N'
END

SELECT @retn_rate = (@active_count / @tot_orders) * 100
SELECT @attr_rate = 100 - @retn_rate

INSERT INTO @attr (enroll_month, cncl_mth, mth, tot_orders, tot_cancel, active_count, attr_rate, retn_rate ) 
           VALUES (@enroll_mth, @cncl_mth, @mth, @tot_orders, @tot_cancel, @active_count, @attr_rate, @retn_rate)

SELECT @old_enroll_mth = @enroll_mth
SELECT @old_mth = @mth
SELECT @old_retn_rate = @retn_rate
SELECT @old_attr_rate = @attr_rate
SELECT @old_active_cnt = @active_count
SELECT @old_cncl_mth = @cncl_mth
SELECT @old_tot_cancel = @tot_cancel

FETCH NEXT FROM att_cursor INTO @enroll_mth, @cncl_mth, @mth, @tot_orders, @tot_cancel
END

CLOSE att_cursor
DEALLOCATE att_cursor

SELECT * FROM @attr

如果您愿意在没有CURSORCTE 的情况下采用另一种方法,那么您可以改用它:

SELECT
    D.Enroll_Month
,   cncl_mnth
,   mth
,   tot_orders
,   tot_cancel
,   tot_orders - total_cancel active_count
,   100 - CAST(CAST((tot_orders - total_cancel) AS DECIMAL(18,2) ) / (tot_orders) * 100  AS DECIMAL(18,2) )  attr_rate
,   CAST(CAST((tot_orders - total_cancel) AS DECIMAL(18,2) ) / (tot_orders) * 100  AS DECIMAL(18,2) ) retn_rate
FROM (
    SELECT
        d.Enroll_Month
    ,   d.cncl_mnth
    ,   d.mth
    ,   s.tot_orders
    ,   d.tot_cancel
    ,   SUM(d.tot_cancel) OVER(PARTITION BY d.Enroll_Month ORDER BY mth ROWS UNBOUNDED PRECEDING) total_cancel
    FROM @summary s
    JOIN @Detail d ON s.Enroll_Month = d.Enroll_Month
) D

【讨论】:

  • 谢谢你是R5!。即使翻转了逻辑,它也不会显示准确的结果集。
  • @PamPetronas 我已经更新了我的答案,并且还提出了另一种不使用光标或 CTE 的方法。 (用给定的样本测试上面的游标,你会得到相同的结果),但我强烈建议使用新方法(没有 CTE 和游标)。
猜你喜欢
  • 2020-08-29
  • 2018-05-12
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多