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

SQL Server中按顺序从资金表扣除现金流金额的实现

解决方案:递归CTE修正现金流分摊逻辑

测试DDL与数据

首先定义测试用的表结构和数据,确保场景匹配:

CREATE TABLE Cashflow (
    CashflowID INT PRIMARY KEY,
    ProjectID INT,
    Amount DECIMAL(18,2),
    TransactionDate DATE
);

CREATE TABLE Funding (
    FundingID INT PRIMARY KEY,
    ProjectID INT,
    Amount DECIMAL(18,2),
    DepletionOrder INT
);

INSERT INTO Cashflow VALUES
(1, 1, 1500.00, '2024-01-01'),
(2, 1, 2500.00, '2024-02-01');

INSERT INTO Funding VALUES
(1, 1, 1000.00, 1),
(2, 1, 2000.00, 2),
(3, 1, 1500.00, 3);

修正后的递归CTE代码

核心是精准计算CashflowFunded字段,严格遵循资金项消耗顺序与剩余结转规则:

WITH OrderedCashflow AS (
    -- 按项目分组,给现金流按交易顺序生成序列
    SELECT 
        CashflowID,
        ProjectID,
        Amount,
        ROW_NUMBER() OVER (PARTITION BY ProjectID ORDER BY TransactionDate, CashflowID) AS CashflowSeq
    FROM Cashflow
),
OrderedFunding AS (
    -- 按项目分组,给资金项按消耗顺序生成序列
    SELECT 
        FundingID,
        ProjectID,
        Amount,
        DepletionOrder,
        ROW_NUMBER() OVER (PARTITION BY ProjectID ORDER BY DepletionOrder) AS FundingSeq
    FROM Funding
),
RecursiveAllocation AS (
    -- 锚点成员:初始化第一笔现金流与第一笔资金项的分摊
    SELECT
        oc.ProjectID,
        oc.CashflowID,
        ofd.FundingID,
        -- 首次分摊金额:取现金流总额与资金项总额的较小值
        CashflowFunded = MIN(oc.Amount, ofd.Amount),
        -- 剩余待分摊现金流
        CashflowRemainingToFund = oc.Amount - MIN(oc.Amount, ofd.Amount),
        -- 剩余资金项金额
        FundingRemaining = ofd.Amount - MIN(oc.Amount, ofd.Amount),
        oc.CashflowSeq,
        ofd.FundingSeq
    FROM OrderedCashflow oc
    JOIN OrderedFunding ofd ON oc.ProjectID = ofd.ProjectID
    WHERE oc.CashflowSeq = 1 AND ofd.FundingSeq = 1

    UNION ALL

    -- 递归成员:分三种场景处理后续分摊
    SELECT
        ra.ProjectID,
        -- 确定当前处理的现金流ID:若当前现金流未处理完则沿用,否则切换到下一笔
        CASE WHEN ra.CashflowRemainingToFund > 0 THEN ra.CashflowID ELSE oc.CashflowID END AS CashflowID,
        -- 确定当前使用的资金项ID:若当前资金未耗尽则沿用,否则切换到下一个
        CASE WHEN ra.FundingRemaining > 0 THEN ra.FundingID ELSE ofd.FundingID END AS FundingID,
        -- 核心:计算本次分摊金额
        CashflowFunded = CASE
            -- 场景1:当前现金流未处理完,当前资金项未耗尽
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining > 0 THEN
                MIN(ra.CashflowRemainingToFund, ra.FundingRemaining)
            -- 场景2:当前现金流已处理完,切换到下一笔现金流,使用剩余资金项
            WHEN ra.CashflowRemainingToFund = 0 AND ra.FundingRemaining > 0 THEN
                MIN(oc.Amount, ra.FundingRemaining)
            -- 场景3:当前资金项已耗尽,切换到下一个资金项,处理剩余现金流
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining = 0 THEN
                MIN(ra.CashflowRemainingToFund, ofd.Amount)
        END,
        -- 更新剩余待分摊现金流
        CashflowRemainingToFund = CASE
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining > 0 THEN
                ra.CashflowRemainingToFund - MIN(ra.CashflowRemainingToFund, ra.FundingRemaining)
            WHEN ra.CashflowRemainingToFund = 0 AND ra.FundingRemaining > 0 THEN
                oc.Amount - MIN(oc.Amount, ra.FundingRemaining)
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining = 0 THEN
                ra.CashflowRemainingToFund - MIN(ra.CashflowRemainingToFund, ofd.Amount)
        END,
        -- 更新剩余资金项金额
        FundingRemaining = CASE
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining > 0 THEN
                ra.FundingRemaining - MIN(ra.CashflowRemainingToFund, ra.FundingRemaining)
            WHEN ra.CashflowRemainingToFund = 0 AND ra.FundingRemaining > 0 THEN
                ra.FundingRemaining - MIN(oc.Amount, ra.FundingRemaining)
            WHEN ra.CashflowRemainingToFund > 0 AND ra.FundingRemaining = 0 THEN
                ofd.Amount - MIN(ra.CashflowRemainingToFund, ofd.Amount)
        END,
        -- 更新现金流序列:当前现金流处理完则+1,否则沿用
        CASE WHEN ra.CashflowRemainingToFund > 0 THEN ra.CashflowSeq ELSE ra.CashflowSeq + 1 END AS CashflowSeq,
        -- 更新资金项序列:当前资金耗尽则+1,否则沿用
        CASE WHEN ra.FundingRemaining > 0 THEN ra.FundingSeq ELSE ra.FundingSeq + 1 END AS FundingSeq
    FROM RecursiveAllocation ra
    -- 关联下一笔现金流(仅当当前现金流已处理完时)
    LEFT JOIN OrderedCashflow oc ON ra.ProjectID = oc.ProjectID AND ra.CashflowSeq + 1 = oc.CashflowSeq
    -- 关联下一个资金项(仅当当前资金项已耗尽时)
    LEFT JOIN OrderedFunding ofd ON ra.ProjectID = ofd.ProjectID AND ra.FundingSeq + 1 = ofd.FundingSeq
    -- 终止条件:所有现金流处理完毕 或 所有资金项耗尽
    WHERE (ra.CashflowRemainingToFund > 0 OR oc.CashflowID IS NOT NULL)
      AND (ra.FundingRemaining > 0 OR ofd.FundingID IS NOT NULL)
)
-- 最终输出:过滤掉分摊金额为0的无效记录,按规则排序
SELECT
    ProjectID,
    CashflowID,
    FundingID,
    CashflowFunded,
    CashflowRemainingToFund,
    FundingRemaining
FROM RecursiveAllocation
WHERE CashflowFunded > 0
ORDER BY ProjectID, CashflowID, FundingID;

CashflowFunded计算逻辑说明

  1. 锚点阶段:直接取第一笔现金流与第一笔资金项的较小值作为初始分摊金额,确保首次消耗符合规则。
  2. 递归阶段:
    • 当现金流和资金项都有剩余时,取两者剩余值的较小值作为本次分摊金额。
    • 当现金流耗尽时,切换到下一笔现金流,用当前剩余资金项覆盖其部分/全部金额。
    • 当资金项耗尽时,切换到下一个资金项,覆盖当前现金流的剩余部分。

该方案完全基于递归CTE实现,避免了游标或循环带来的性能损耗,同时严格遵循资金项消耗顺序、剩余资金结转的业务规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:36:14