1

私は会計システムを構築しました.SQLテーブルでは、次の方法で金額と残高の2つの列があります。

ID 残高残高  
101 20 100  
102 20 80  
103 10 70  
104 30 40  
105 25 15  
106 5 10

そのような: Balance Left = previous Balance Left - Amount.

ここで、ID が 103 の行を削除すると、後続のすべての行に金額 10 を追加する必要があります。それを行うための最良の方法は何ですか (SQL Server 2008 またはそれ以外で)?

PS :

  1. 各行を更新するトリガーを追加できることは承知していますが、より良い代替手段を探しています

  2. C# でコードします。

いくつかの質問と一般的な回答への回答: 1. 削除した行の下に 100 万行あることを考えると、LINQ を介して C# で実行する方がよいでしょうか? それとも SQL トリガーの方が速く動作しますか? 2. 各エントリの残高を保存するのはばかげていることはわかっていますが、DB/ソフトウェアのユーザーがそれを要求する方法です。

4

4 に答える 4

5

最善の方法は、残高を保存せず、必要に応じてビューで計算することです。

ただし、それが望ましくない、または可能でない場合は、データの一貫性を確保できるため、トリガーが最善の策です。実際には、テーブルへのすべての書き込みアクセスを削除し、ストアド プロシージャを介して変更を強制することもうまく機能しますが、一度に 1 行の更新と削除に制限されます。

疑似コード形式のクエリは 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();
}

ここでの利点は次のとおりです。

  • 単一のトランザクション - 更新全体が行われるか、まったく行われません。
  • 楽観的な同時実行性 - 読み取りと書き込みの間に誰かが行の 1 つを削除または更新すると、変更は拒否されます。
  • 保守可能な C# コードをクリアします。

欠点は次のとおりです。

  • レコードは、更新する前にメモリに読み込む必要があります。
  • 行ごとに 1 つの更新ステートメントが生成されます。

更新する行が 100 万行あるので、上記の方法は遅くて無駄が多いです。行をメモリに読み込まずに、より少ない 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 に答える