You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在SQL Server中实现表格累计折扣计算的技术问询

在SQL Server中实现累计折扣计算

需要为表格计算DISCOUNT_AMOUNT和AMOUNT_REST,规则如下:

  • 每个NAME对应唯一的AMOUNT
  • 按DATE升序排列的第一行,折扣金额为AMOUNT * DISCOUNT%,剩余金额为AMOUNT * (1 - DISCOUNT%)
  • 同NAME的后续行,折扣金额基于上一行的AMOUNT_REST计算,即上一行AMOUNT_REST * 当前DISCOUNT%,剩余金额为上一行AMOUNT_REST * (1 - 当前DISCOUNT%)

现有基础查询语句:

select [NAME], [AMOUNT], [DISCOUNT%], [DATE]--, DISCOUNT_AMOUNT, AMOUNT_REST
FROM [Table]
order by [NAME], [DATE] asc

期望计算结果

[NAME][AMOUNT][DATE] (dd-MM-yyyy)[DISCOUNT%][DISCOUNT_AMOUNT][AMOUNT_REST]
Peter$10001-01-20234%$4,0$96,0
Peter$10002-01-202320%$19,2$76,8
Peter$10003-01-20235%$3,8$73,0
John$50001-01-202340%$200,0$300,0
John$50002-01-20233%$9,0$291,0
Sara$20001-01-20239%$18,0$182,0
Sara$20002-01-202310%$18,2$163,8

Excel手动计算逻辑

[NAME][AMOUNT][DATE] (Excel序列号)[DISCOUNT%][DISCOUNT_AMOUNT][AMOUNT_REST]
Peter100449270,04B2*D2B2*(1-D2)
Peter100449280,2F2*D3F2*(1-D3)
Peter100449290,05F3*D4F3*(1-D4)
John500449270,4B5*D5B5*(1-D5)
John500449280,03F5*D6F5*(1-D6)
Sara200449270,09B7*D7B7*(1-D7)
Sara200449280,1F7*D8F7*(1-D8)

SQL Server实现方案

方法一:递归CTE(模拟Excel逐行计算)

该方法逻辑和Excel完全一致,逐行递归计算,直观易维护:

WITH RankedData AS (
    SELECT 
        [NAME],
        [AMOUNT],
        [DISCOUNT%],
        [DATE],
        -- 将带%的折扣转换为小数
        CAST(REPLACE([DISCOUNT%], '%', '') AS DECIMAL(18,2))/100 AS DiscountRate,
        -- 将带$的金额转换为数值
        CAST(REPLACE([AMOUNT], '$', '') AS DECIMAL(18,2)) AS OriginalAmount,
        -- 按用户分组、日期升序标记行号
        ROW_NUMBER() OVER(PARTITION BY [NAME] ORDER BY [DATE] ASC) AS RowNum
    FROM [Table]
),
RecursiveDiscount AS (
    -- 初始行:每组第一行的计算
    SELECT 
        [NAME],
        [AMOUNT],
        [DISCOUNT%],
        [DATE],
        OriginalAmount * DiscountRate AS DISCOUNT_AMOUNT,
        OriginalAmount * (1 - DiscountRate) AS AMOUNT_REST,
        RowNum
    FROM RankedData
    WHERE RowNum = 1

    UNION ALL

    -- 递归计算后续行:基于上一行的剩余金额
    SELECT 
        rd.[NAME],
        rd.[AMOUNT],
        rd.[DISCOUNT%],
        rd.[DATE],
        rd.DiscountRate * r.AMOUNT_REST AS DISCOUNT_AMOUNT,
        r.AMOUNT_REST * (1 - rd.DiscountRate) AS AMOUNT_REST,
        rd.RowNum
    FROM RankedData rd
    INNER JOIN RecursiveDiscount r ON rd.[NAME] = r.[NAME] AND rd.RowNum = r.RowNum + 1
)
SELECT 
    [NAME],
    [AMOUNT],
    FORMAT([DATE], 'dd-MM-yyyy') AS [DATE (dd-MM-yyyy)],
    [DISCOUNT%],
    -- 格式化金额为带$的字符串,保留1位小数
    '$' + CAST(ROUND(DISCOUNT_AMOUNT, 1) AS VARCHAR) AS DISCOUNT_AMOUNT,
    '$' + CAST(ROUND(AMOUNT_REST, 1) AS VARCHAR) AS AMOUNT_REST
FROM RecursiveDiscount
ORDER BY [NAME], [DATE] ASC;

方法二:窗口函数累计乘积(适合大数据量)

利用对数和指数计算累计的(1-折扣率)乘积,性能比递归CTE更优,适用于SQL Server 2012及以上版本:

WITH DataWithRates AS (
    SELECT 
        [NAME],
        [AMOUNT],
        [DISCOUNT%],
        [DATE],
        CAST(REPLACE([DISCOUNT%], '%', '') AS DECIMAL(18,2))/100 AS DiscountRate,
        CAST(REPLACE([AMOUNT], '$', '') AS DECIMAL(18,2)) AS OriginalAmount,
        ROW_NUMBER() OVER(PARTITION BY [NAME] ORDER BY [DATE] ASC) AS RowNum
    FROM [Table]
),
CumulativeFactors AS (
    SELECT 
        *,
        -- 计算当前及之前所有行的(1-DiscountRate)乘积
        EXP(SUM(LOG(1 - DiscountRate)) OVER(PARTITION BY [NAME] ORDER BY [DATE] ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)) AS CumulativeFactor,
        -- 计算上一行为止的(1-DiscountRate)乘积,第一行默认值为1
        ISNULL(EXP(SUM(LOG(1 - DiscountRate)) OVER(PARTITION BY [NAME] ORDER BY [DATE] ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)), 1) AS PreviousCumulativeFactor
    FROM DataWithRates
)
SELECT 
    [NAME],
    [AMOUNT],
    FORMAT([DATE], 'dd-MM-yyyy') AS [DATE (dd-MM-yyyy)],
    [DISCOUNT%],
    '$' + CAST(ROUND(
        CASE 
            WHEN RowNum = 1 THEN OriginalAmount * DiscountRate
            ELSE OriginalAmount * PreviousCumulativeFactor * DiscountRate
        END, 1) AS VARCHAR) AS DISCOUNT_AMOUNT,
    '$' + CAST(ROUND(OriginalAmount * CumulativeFactor, 1) AS VARCHAR) AS AMOUNT_REST
FROM CumulativeFactors
ORDER BY [NAME], [DATE] ASC;

注意事项

  • 如果原表中的AMOUNT和DISCOUNT%已经是数值类型,可直接去掉字符串转换步骤
  • 若存在折扣率为100%的情况,方法二需要额外处理(避免LOG(0)报错)

内容的提问来源于stack exchange,提问作者Alan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 03:57:05