如何使用T-SQL重复多列的计算?

时间:2011-02-03 22:00:07

标签: sql sql-server tsql

我正在尝试按月计算客户流失率。我有一个表customer_key,12个月标志作为列。我需要一个结果输出表来显示:月份编号(1-12),分组字段(任期带:< 1年,1-3岁,3-5岁,5岁以上)和流失计数。例如:

月任期流失

第1个月< 1yr 1,234;

第2个月< 1yr 656;

...

第12个月< 1yr 777;

流失计数的计算方法是减去一个月内“存在”的客户数量减去下个月“存在”的数量,由Mon1_Basic_Flag和Mon2_Basic_Flag表示。目前,我使用以下代码来获得此结果:

                 SELECT
                    'M01' AS Monthnumber
                    ,case when DATEDIFF(month,inception_dt,@fmonth)<12 then '<1yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 12 and 36 then '1-3yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 36 and 60 then '3-5yr'
                      when DATEDIFF(month,inception_dt,@fmonth)>60 then '>5yr' end as tenureband
                  ,SUM(CASE WHEN MON1_BASIC_FLAG >0 THEN 1 ELSE 0 END) -SUM(CASE WHEN MON2_BASIC_FLAG>0 then 1 else 0 end
                    as churn 
                from dbo.customers
                group by inception_dt

                union all

              SELECT
                    'M02' AS Monthnumber
                    ,case when DATEDIFF(month,inception_dt,@fmonth)<12 then '<1yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 12 and 36 then '1-3yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 36 and 60 then '3-5yr'
                      when DATEDIFF(month,inception_dt,@fmonth)>60 then '>5yr' end as tenureband
                  ,SUM(CASE WHEN MON2_BASIC_FLAG >0 THEN 1 ELSE 0 END) -SUM(CASE WHEN MON3_BASIC_FLAG>0 then 1 else 0 end
                    as churn 
                from dbo.customers
                group by inception_dt

                union all

                ....

                union all

              SELECT
                    'M11' AS Monthnumber
                    ,case when DATEDIFF(month,inception_dt,@fmonth)<12 then '<1yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 12 and 36 then '1-3yr'
                      when DATEDIFF(month,inception_dt,@fmonth) between 36 and 60 then '3-5yr'
                      when DATEDIFF(month,inception_dt,@fmonth)>60 then '>5yr' end as tenureband
                  ,SUM(CASE WHEN MON11_BASIC_FLAG >0 THEN 1 ELSE 0 END) -SUM(CASE WHEN MON12_BASIC_FLAG>0 then 1 else 0 end
                    as churn 
                from dbo.customers
                group by inception_dt 

然而,重复12次,此代码在进行任何更改时会留下很大的错误空间。我想把它放在一个循环中,以便它每个月重复相同的计算。我可以很容易地在SAS中做到这一点,但我已经到处寻找这个概念的SQL翻译。有什么建议? 谢谢!

6 个答案:

答案 0 :(得分:2)

你可以create a function做到这一点。

CREATE TABLE Customers
(
    Inception datetime,
    MON1_BASIC_FLAG int, MON2_BASIC_FLAG int, MON3_BASIC_FLAG int, MON4_BASIC_FLAG int,
    MON5_BASIC_FLAG int, MON6_BASIC_FLAG int, MON7_BASIC_FLAG int, MON8_BASIC_FLAG int,
    MON9_BASIC_FLAG int, MON10_BASIC_FLAG int, MON11_BASIC_FLAG int, MON12_BASIC_FLAG int
)
INSERT INTO Customers VALUES ('2010-01-01', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
GO

CREATE FUNCTION TenureBand(@startDate datetime, @endDate datetime)
RETURNS varchar(5)
BEGIN
    DECLARE @diff int
    SELECT  @diff = DATEDIFF(month, @startDate, @endDate)
    RETURN CASE
        WHEN @diff < 12 then '<1yr'
        WHEN @diff BETWEEN 12 AND 36 THEN '1-3yr'
        WHEN @diff BETWEEN 36 AND 60 THEN '3-5yr'
        ELSE '>5yr'
    END
END
GO

SELECT  Inception,
        [01] - [02] as [M01], [02] - [03] as [M02], [03] - [04] as [M03],
        [04] - [05] as [M04], [05] - [06] as [M05], [06] - [07] as [M06],
        [07] - [08] as [M07], [08] - [09] as [M08], [09] - [10] as [M09],
        [10] - [11] as [M10], [11] - [12] as [M11]
INTO    #TempTable
FROM    (
        SELECT  Inception,
                SUM(MON1_BASIC_FLAG) as [01], SUM(MON2_BASIC_FLAG) as [02],
                SUM(MON3_BASIC_FLAG) as [03], SUM(MON4_BASIC_FLAG) as [04],
                SUM(MON5_BASIC_FLAG) as [05], SUM(MON6_BASIC_FLAG) as [06], 
                SUM(MON7_BASIC_FLAG) as [07], SUM(MON8_BASIC_FLAG) as [08],
                SUM(MON9_BASIC_FLAG) as [09], SUM(MON10_BASIC_FLAG) as [10],
                SUM(MON11_BASIC_FLAG) as [11], SUM(MON12_BASIC_FLAG) as [12]
        FROM Customers
        GROUP BY Inception
) p

SELECT  Inception, 
        [#TempTable] as [MonthNumber], 
        dbo.TenureBand(Inception, getdate()) AS [TenureBand], [Churn]
FROM    (   SELECT  Inception, 
                    [M01], [M02], [M03], [M04], [M05], [M06],
                    [M07], [M08], [M09], [M10], [M11]
            FROM    #TempTable
        ) pvt
UNPIVOT (   [Churn] FOR #TempTable IN
        (   [M01], [M02], [M03], [M04], [M05], [M06], 
            [M07], [M08], [M09], [M10], [M11])
        ) as unpv

答案 1 :(得分:1)

;WITH cte AS (  /* comment this line out for SQL Server version under 2005 */
  SELECT
    m.MonthInt,
    m.MonthNumber,
    DATEDIFF(month, c.inception_dt, @fmonth) AS MonthDiff,
    COUNT(
      CASE
        WHEN m.MonthInt =  1 AND MON01_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  2 AND MON02_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  3 AND MON03_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  4 AND MON04_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  5 AND MON05_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  6 AND MON06_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  7 AND MON07_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  8 AND MON08_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt =  9 AND MON09_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt = 10 AND MON10_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt = 11 AND MON11_BASIC_FLAG > 0 THEN 1
        WHEN m.MonthInt = 12 AND MON12_BASIC_FLAG > 0 THEN 1
      END
    ) AS MonthFlagCount
  /*INTO #cte*/  /* uncomment this for SQL Server version under 2005 */
  FROM (
    SELECT  1, 'M01' UNION ALL
    SELECT  2, 'M02' UNION ALL
    SELECT  3, 'M03' UNION ALL
    SELECT  4, 'M04' UNION ALL
    SELECT  5, 'M05' UNION ALL
    SELECT  6, 'M06' UNION ALL
    SELECT  7, 'M07' UNION ALL
    SELECT  8, 'M08' UNION ALL
    SELECT  9, 'M09' UNION ALL
    SELECT 10, 'M10' UNION ALL
    SELECT 11, 'M11' UNION ALL
    SELECT 12, 'M12'
  ) AS m (MonthInt, MonthNumber)
    CROSS JOIN dbo.customers c ON
  GROUP BY m.MonthInt, m.MonthNumber, c.inception_dt
)  /* comment this line out for SQL Server version under 2005 */
SELECT
  t1.MonthNumber,
  CASE
    WHEN MonthDiff < 12 THEN '<1yr'
    WHEN MonthDiff <= 36 THEN '1-3yr'
    WHEN MonthDiff <= 60 THEN '3-5yr'
    ELSE '>5yr'
  END AS tenureband,
  t1.MonthGlagCount - t2.MonthFlagCount AS churn
FROM cte t1
  INNER JOIN cte t2 ON t1.MonthInt = t2.MonthInt + 1
/*DROP TABLE #cte*/  /* uncomment this for SQL Server version under 2005 */

编辑:当然,如果最终选择中的SQL Server 2000及更早版本cte也应替换为#cte

答案 2 :(得分:0)

真的,只是笛卡尔加入了一个包含12行的表格。

创建一个名为row_number的列的表。

用12行填充。

将笛卡尔表格放入您的查询中。

选择“M”&amp; ROW_NUMBER

您必须填写row_number以获取M02的0

顺便说一句,生成12个数字的表有一些比较简单的方法,但这是一种简单的学习方法,你以后总是能够重构。

答案 3 :(得分:0)

不确定这是否更好,但是当您需要编辑查询时,将它们保持在一起可能有所帮助。

SELECT
    M.M AS Monthnumber
    ,case when DATEDIFF(month,inception_dt,@fmonth)<12 then '<1yr'
      when DATEDIFF(month,inception_dt,@fmonth) between 12 and 36 then '1-3yr'
      when DATEDIFF(month,inception_dt,@fmonth) between 36 and 60 then '3-5yr'
      when DATEDIFF(month,inception_dt,@fmonth)>60 then '>5yr' end as tenureband
  ,SUM(CASE WHEN CASE M.M
    WHEN 'M01' THEN MON1_BASIC_FLAG
    WHEN 'M02' THEN MON2_BASIC_FLAG
    --..
    WHEN 'M11' THEN MON11_BASIC_FLAG
    END > 0 THEN 1 ELSE 0 END) -
   SUM(CASE WHEN CASE M.M
    WHEN 'M01' THEN MON2_BASIC_FLAG
    WHEN 'M02' THEN MON3_BASIC_FLAG
    --..
    WHEN 'M11' THEN MON12_BASIC_FLAG
    END > 0 then 1 else 0 end)
    as churn 
from dbo.customers
cross join (
    select 'M01' as M union all select 'M02' union all
    select 'M03' union all select 'M04' union all
    select 'M05' union all select 'M06' union all
    select 'M07' union all select 'M08' union all
    select 'M09' union all select 'M10' union all
    select 'M11') M
group by inception_dt, M.M

如果您使用的是SQL Server 2008,则可以使用UNPIVOT运算符来提供帮助。

答案 4 :(得分:-1)

您可以这样做:

declare @m int
set @m = 1

while @m <= 12
begin
    -- Construct and run dynamic SQL query

    set @m = @m + 1
end

答案 5 :(得分:-1)

你可以做下面的事吗?

它不完整但如果您可以加入CTE和现有查询之间,那么您可能可以完成您正在尝试的内容......

declare @count int;
set @count = 1;
WITH Months AS (
        SELECT
         [Month] = @count
        UNION ALL 
        SELECT
         [Month] = [Month] + 1
        FROM
         Months
        WHERE
         [Month]< 12)

 SELECT 'M' + CAST([Month] as varchar(2))
                ,case when DATEDIFF(month,inception_dt,@fmonth)<12 then '<1yr'
                  when DATEDIFF(month,inception_dt,@fmonth) between 12 and 36 then '1-3yr'
                  when DATEDIFF(month,inception_dt,@fmonth) between 36 and 60 then '3-5yr'
                  when DATEDIFF(month,inception_dt,@fmonth)>60 then '>5yr' end as tenureband
              ,SUM(CASE WHEN MON1_BASIC_FLAG >0 THEN 1 ELSE 0 END) -SUM(CASE WHEN MON2_BASIC_FLAG>0 then 1 else 0 end
                as churn 
            from dbo.customers
            --JOIN TO Months CTE???
            group by inception_dt
   OPTION (MAXRECURSION 12)