1

I have built an accounting system, where in a SQL Table, two of the columns are : Amount and Balance Left in the following manner :

ID    Amount    Balance Left  
101    20        100  
102    20         80  
103    10         70  
104    30         40  
105    25         15  
106     5         10

Such that: Balance Left = previous Balance Left - Amount.

Now if I delete a row with Id say 103, I would want amount 10 added to all the subsequent rows. What is the best possible way to do that (in SQL Server 2008 or otherwise)?

P.S. :

  1. I'm aware that I can add a trigger to update each row, but looking for a better alternative, if any.

  2. Code in C#.

In reply to some of the questions and common answers : 1. Considering there are a million rows below my deleted row - Would it be better to do it in C# via LINQ? or would a SQL trigger work faster? 2. I know its preposterous to save the balance left with each entry, but thats how the user of the DB/software demands it.

4

4 回答 4

5

最好的方法是不存储剩余余额,而是在需要时在视图中计算。

但是,如果这不是可取的或不可能的,那么触发器是您最好的选择,因为您可以确保您的数据保持一致。实际上,删除对表的所有写访问权并通过存储过程强制更改也很有效,尽管您仅限于一次一行的更新和删除。

伪代码格式的查询将是 update MyTable set BalanceLeft = BalanceLeft + RemovedAmount where ID > RemovedID

如果您希望此表上有多个读取器/写入器,那么您还需要考虑锁定并确保删除和后续更新在同一个事务中。如果您让视图计算剩余余额,大多数这些问题都会消失。

于 2012-12-10T13:15:46.733 回答
2

我不知道“最好的”,但这里是如何在 linq to sql 中做到这一点。

using (CustomDataContext dc = new CustomDataContext())
{
  List<Row> rows = dc.Rows
    .Where(row => startingID <= row.ID)
    .OrderBy(row => row.ID)
    .ToList();

  Row firstRow = rows.First();
  int firstRowAmount = firstRow.Amount;
  dc.Rows.DeleteOnSubmit(firstRow);

  foreach(Row row in rows.Skip(1))
  {
    row.Amount -= firstRowAmount;
  }
  dc.SubmitChanges();
}

这里的优点是:

  • 单笔交易 - 整个更新完成或没有。
  • 乐观并发 - 如果有人在我们的读取和写入之间删除或更新我们的行之一,我们的更改将被拒绝。
  • 清除可维护的 C# 代码。

缺点是:

  • 记录必须先读入内存,然后才能更新。
  • 每行生成一个更新语句。

由于您有一百万行要更新,因此上述内容有点慢且浪费……您将不得不在更少的 sql 命令中更新这些行,而无需将这些行读入内存。

using(TransactionScope ts = new TransactionScope())
{
  using (CustomDataContext dc = new CustomDataContext())
  {
    Row theRow = dc.Rows.Single(row => startingID == row.ID)

    int firstRowAmount = theRow.Amount;
    dc.Rows.DeleteOnSubmit(theRow);
    dc.SubmitChanges();
    dc.ExecuteCommand(@"Update Row SET Amount = Amount - {0} WHERE {1} < ID", firstRowAmount, startingID);
    ts.Complete();
  }
}
于 2012-12-10T13:17:08.243 回答
1
DECLARE @T TABLE
(
    ID INT,
    Amount INT,
    [Balance Left] INT

)

INSERT INTO @T VALUES
(101,20,100),
(102,20,80),
(103,10,70),
(104,30,40),
(105,25,15),
(106,5,10)

DECLARE @ID_DELETE INT = 103
DECLARE @AMOUNT_DELETED INT = 0

SELECT @AMOUNT_DELETED = Amount
FROM @T WHERE ID = @ID_DELETE

DELETE FROM @T WHERE ID = @ID_DELETE

UPDATE @T SET [Balance Left] = [Balance Left] + @AMOUNT_DELETED WHERE ID > @ID_DELETE

SELECT * FROM @T
于 2012-12-10T13:13:13.530 回答
0

好吧,首先您不需要单独更新每一行,而是可以有一个删除触发器,将所有基础行一起更新:

CREATE TRIGGER sampleTrigger
        ON database1.dbo.balanceTable
        FOR DELETE
    AS
      UPDATE balanceTable t
      SET t.BalanceLeft = t.BalanceLeft + deleted.Amount
      Where t.ID > deleted.ID
    GO

或者如果您根本不想使用触发器,您可以创建一个包含更新的存储过程,然后在您的应用程序中在删除之前调用它。但是在后者中,您应该在单个事务中执行它们以确保保持数据完整性。

于 2012-12-10T13:25:23.387 回答