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

SQL Server 2014多行更新时如何高效维护多日期累计求和值?

Hey Keith, great question—dealing with running totals in large SQL Server 2014 tables while keeping updates efficient is a common pain point for reconciliation systems. Let’s break down an elegant, performant solution that avoids recalculating from the start every time, tailored to your account transaction use case.

Key Problem Recap

You’ve got a table storing transaction records with precomputed running totals (one per date/account), but:

  • The table is massive, so recalculating running totals from the earliest date on every update is too slow
  • Users need to query arbitrary date ranges without waiting for full recalculations
  • You need to keep running totals in sync automatically when multiple rows are updated/inserted/deleted

Solution 1: Trigger-Based Incremental Update (Synchronous)

The best approach here is to use an AFTER trigger that only recalculates running totals for the affected date range, not the entire table. This ensures updates are synchronous and minimizes redundant computation.

Step 1: Ensure Proper Indexing

First, add a clustered index on AccountID + TransactionDate—this is critical for fast range queries and sorting when recalculating totals:

CREATE CLUSTERED INDEX IX_AccountTransactions_AccountID_Date 
ON AccountTransactions(AccountID, TransactionDate);

Step 2: Create the Trigger

This trigger captures all affected accounts and their date ranges, then recalculates running totals only from the earliest affected date onward for those accounts:

CREATE TRIGGER trg_SyncRunningTotals ON AccountTransactions
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;

    -- Capture all accounts and their min/max affected dates
    DECLARE @AffectedAccounts TABLE (
        AccountID INT,
        MinAffectedDate DATE,
        MaxAffectedDate DATE
    );

    INSERT INTO @AffectedAccounts
    SELECT
        COALESCE(i.AccountID, d.AccountID) AS AccountID,
        MIN(COALESCE(i.TransactionDate, d.TransactionDate)) AS MinAffectedDate,
        MAX(COALESCE(i.TransactionDate, d.TransactionDate)) AS MaxAffectedDate
    FROM inserted i
    FULL OUTER JOIN deleted d 
        ON i.AccountID = d.AccountID 
        AND i.TransactionDate = d.TransactionDate
    GROUP BY COALESCE(i.AccountID, d.AccountID);

    -- Recalculate running totals for affected accounts starting from MinAffectedDate
    UPDATE t
    SET RunningTotal = new_totals.CumulativeTotal
    FROM AccountTransactions t
    INNER JOIN (
        SELECT
            AccountID,
            TransactionDate,
            SUM(TransactionAmount) OVER (
                PARTITION BY AccountID 
                ORDER BY TransactionDate 
                ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
            ) AS CumulativeTotal
        FROM AccountTransactions
        WHERE EXISTS (
            SELECT 1 FROM @AffectedAccounts aa
            WHERE aa.AccountID = AccountTransactions.AccountID
            AND AccountTransactions.TransactionDate >= aa.MinAffectedDate
        )
    ) new_totals 
        ON t.AccountID = new_totals.AccountID 
        AND t.TransactionDate = new_totals.TransactionDate
    WHERE EXISTS (
        SELECT 1 FROM @AffectedAccounts aa
        WHERE aa.AccountID = t.AccountID
        AND t.TransactionDate >= aa.MinAffectedDate
    );
END;

How This Works

  1. Capture Affected Ranges: The trigger uses a FULL OUTER JOIN between inserted and deleted tables to find all accounts that had changes, plus the earliest and latest dates affected by those changes.
  2. Incremental Recalculation: Instead of recalculating from the first transaction ever, we only recompute running totals starting from the earliest affected date for each account. This cuts down computation drastically for large tables.
  3. Set-Based Operations: No cursors here—everything uses set-based logic, which is critical for performance with large datasets.

Optimization Tips

  • Batch Updates: If you’re doing bulk updates (e.g., importing transactions), disable the trigger temporarily, run the bulk insert/update, then manually trigger the recalculation for the affected ranges. This reduces trigger overhead.
  • Partition the Table: If your table is truly massive (millions/billions of rows), partition it by AccountID or TransactionDate. This makes range scans even faster and reduces lock contention.
  • Avoid Over-Triggers: If you have frequent small updates, consider grouping them into batches to minimize how often the trigger runs.

Important Considerations

  • Transaction Consistency: The trigger runs in the same transaction as the update/insert/delete, so if the recalculation fails, the entire operation rolls back—perfect for reconciliation systems where data integrity is non-negotiable.
  • Concurrency: The clustered index helps minimize lock scope, but for high-concurrency systems, test with load to ensure locks don’t become a bottleneck. You might need to adjust isolation levels or use snapshot isolation if needed.
  • Edge Cases: Test scenarios like inserting a transaction earlier than existing dates, deleting a middle transaction, or updating multiple dates for the same account—this trigger handles all of these by resetting totals from the earliest affected point.

Alternative: Batch-Processed Snapshot Updates

If synchronous updates are too slow for your peak load, you could:

  1. Store daily running total snapshots in a separate table
  2. Use a SQL Agent job to recalculate snapshots for affected dates on a schedule (e.g., every 15 minutes)
  3. Point user queries to the snapshot table for fast reads

This trades strict sync for better write performance—only use this if your reconciliation process can tolerate a small delay in updated totals.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:13:08