动态压缩相同 SQL table 中的不同行

Dynamically compre different rows in same SQL table

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

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

我想它应该是动态构建的(我说得对吗?可以不用动态构建吗?sql),在网上搜索时看到了不同的方法:

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

  2. 为要比较的每个月将 UNIONS 动态添加到相同的 table

  3. 为要比较的每个月动态添加 LEFT JOINS 到相同的 table

方法 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

Status = 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 查询,该查询循环创建用户定义的几个月前的案例。需要很长时间...我错过了什么? LEFT JOIN 或 UNIONS 会更好吗?

我自己想出来了。希望这对其他人有帮助...

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

那是圣运行ge。 (也许服务器在第一个 运行 之后适应?)

从那以后它运行得非常快,几个月前不到一秒。

这是我所做的: (改进和风格评论将不胜感激!)

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)