动态地压缩同一SQL表中的不同行

时间:2019-04-02 08:36:55

标签: sql tsql sql-server-2008-r2

客户需要一种工具来显示几个月前客户每月的不成功付款。开始月份和返回的月份数应由用户定义 所有付款都记录在同一张表中。 同一客户在过去几个月中可能有也可能没有付款。 这是一张表格,每笔付款都有多行,所以我需要使用分组依据。

我正在尝试找出查询信息的最佳方法。

我想它应该是动态构建的(对吗?可以在没有动态sql的情况下完成吗?),并且在搜索网络时看到了不同的方法:

  1. 使用案例,例如以下查询,在select子句中动态添加行:SQL query to compare product sales by month

  2. 将每个月要比较的UNIONS动态添加到同一张表中

  3. 将每个月要比较的左联接动态添加到同一张表中

方法1的概念代码:

DECLARE @month int, @monthNum int
SET @month = 103
SET @monthNum = 1

SELECT
    name,ID,Property
    ,MAX(CASE WHEN month = @month THEN month ELSE 0 END) AS month 
    ,SUM(CASE WHEN month = @month THEN paymentSum ELSE 0 END) AS paymentSum 
    ,MAX(CASE WHEN month = @month THEN PaymentFailReason ELSE 0 END) AS PaymentFailReason
    ,MAX(CASE WHEN month = @month -1 THEN month ELSE 0 END) AS monthMinusOne
    ,SUM(CASE WHEN month = @month -1 THEN paymentSum ELSE 0 END) AS paymentSumMinusOne
    ,MAX(CASE WHEN month = @month -1 THEN PaymentFailReason ELSE 0 END) AS PaymentFailReasonMinusOne

FROM
    payments

WHERE
    month BETWEEN @month -@monthNum AND @month AND status = 2

GROUP BY
name,ID,Property

状态= 2表示付款失败。

在这种情况下是否有首选的解决方案?

一种方法比其他方法更有效吗?

样本数据:

month   ID  name    Property    paymentSum  PaymentFailReason   Status
100      1  Aron       A            100       Has No money         2
100      2  Burt       B            100       Has No money         2
100      3  Carl       C            50        Has No money         2
101      1  Aron       A            50        Has No money         2
101      2  Burt       B            50        Has No money         2
101      3  Carl       C            50        Has No money         2
102      1  Aron       A            100       Has No money         2
102      2  Burt       B            100                            1
102      3  Carl       C            100       Has No money         2
103      1  Aron       A            102       Has No money         2
103      2  Burt       B            102       Has No money         2
103      3  Carl       C            102                            1 

查询上述第103个月和1个月前的数据的结果应为:

month   ID  name    Property    paymentSum  PaymentFailReason   Status  monthMinusOne   paymentSumMinusOne  PaymentFailReasonMinusOne
103      1  Aron        A            102       Has No money        2         102                100               Has No money
103      2  Burt        B            102       Has No money        0         102                0                 0

卡尔成功支付了103英镑,因此他不在结果中。

Burt在102日成功付款,因此该月的数据无关紧要(全为零)。

编辑: 我创建了一个动态SQL查询,该查询循环循环创建用户定义的月份数的案例。需要一段时间...我想念什么?左联接或工会会更好吗?

1 个答案:

答案 0 :(得分:0)

自己弄清楚。希望这对其他人有帮助...

选项1显然效果很好。由于某种原因,当我第一次在SSMS中运行它时,花了20个月才花了10秒钟以上的时间-但这只是第一次运行。

这很奇怪。 (也许服务器在第一次运行后就适应了?)

从那时起,它运行起来非常快,几个月后又不到一秒钟。

这是我所做的: (改进和样式说明将不胜感激!)

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[CompareFailedPayments](@month int , @NumOfMonths int) 
AS

BEGIN
DECLARE @SqlHead nvarchar(max), @Sql nvarchar(max)='', @Crlf nvarchar(max), @counter int
    SET @counter = 0
    SET @Crlf = CHAR(13)+CHAR(10)

SET @SqlHead = 'DECLARE @month int = '+ CAST(@month AS VARCHAR) + @Crlf +
    'SELECT name,IDnumber,Property, ID, month, paymentsum, PaymentFailReason, EncryptedCardNum '

    SET @Sql =  'SELECT name,IDnumber,Property,Payments.ID as Id, Payments.Property as p'+ @Crlf +
    ',MAX(CASE WHEN month = @month THEN month ELSE NULL END) AS month '+ @Crlf +
    ',SUM(CASE WHEN month = @month THEN paymentsum ELSE NULL END) AS paymentsum '+ @Crlf +
    ',MAX(CASE WHEN month = @month THEN PaymentFailReason ELSE NULL END) AS PaymentFailReason '+ @Crlf +
    ',MAX(CASE WHEN month = @month THEN RIGHT(EncryptedCardNum,4) ELSE NULL END) AS EncryptedCardNum '+ @Crlf +
    'FROM Payments '+ @Crlf +
    'INNER JOIN'+ @Crlf +
    'client ON Payments.ID = client.ID'+ @Crlf +
    'WHERE (Payments.month = '+ CAST(@month AS VARCHAR) +') AND (Payments.status = 2)'+ @Crlf +
    'GROUP BY client.name, client.IDnumber, Payments.Property,Payments.ID, Payments.Property '+ @Crlf +
    ') AS Base'+ @Crlf +
    'LEFT OUTER JOIN'+ @Crlf +
    '('+ @Crlf +
    'SELECT Payments.ID as Id1, Payments.Property as p1'+ @Crlf+ @Crlf

    WHILE @counter < @NumOfMonths

        BEGIN
        SET @counter = @counter+1

        SET @Sql = @Sql+',MAX(CASE WHEN month = @month-'+CAST(@counter AS VARCHAR)+' THEN month'+' ELSE NULL END) AS month'+CAST(@counter AS VARCHAR)+ @Crlf +
        ',SUM(CASE WHEN month = @month-'+CAST(@counter AS VARCHAR)+' THEN paymentsum ELSE NULL END) AS paymentsum'+CAST(@counter AS VARCHAR)+ @Crlf +
        ',MAX(CASE WHEN month = @month-'+CAST(@counter AS VARCHAR)+' THEN PaymentFailReason ELSE NULL END) AS PaymentFailReason'+CAST(@counter AS VARCHAR)+ @Crlf +
        ',MAX(CASE WHEN month = @month-'+CAST(@counter AS VARCHAR)+' THEN RIGHT(EncryptedCardNum,4) ELSE NULL END) AS EncryptedCardNum'+CAST(@counter AS VARCHAR)+ @Crlf + @Crlf 

         SET @SqlHead = @SqlHead +', month'+CAST(@counter AS VARCHAR)+', paymentsum'+CAST(@counter AS VARCHAR)+', PaymentFailReason'+CAST(@counter AS VARCHAR)+', EncryptedCardNum'+CAST(@counter AS VARCHAR)+''

        END

     SET @SqlHead = @SqlHead +'  FROM(' + @Crlf

     SET @Sql = @Sql+
    'FROM            Payments'+ @Crlf +
    'WHERE        (Payments.month BETWEEN '+CAST(@month-@NumOfMonths AS VARCHAR)+' AND ' +CAST(@month-1 AS VARCHAR)+') AND (Payments.status = 2)'+ @Crlf +
    'GROUP BY Payments.ID, Payments.Property'+ @Crlf +
    ') AS Back '+ @Crlf +
    'ON Base.ID = Back.m1 and Base.h = Back.h1'  

--PRINT(@sqlHead+@Sql)

EXEC(@sqlHead+@Sql)