4

私は今日と昨日のかなりの部分を、SQL でループまたはカーソルを使用するかどうかを決定するか、問題を解決するためにセット ベースのロジックを使用する方法を理解することに費やしました。私はロジックを設定するのは初めてではありませんが、この問題は特に複雑に思えます。

問題

アイデアは、すべてのトランザクション (数千、数億) のリストとそれらが発生した日付がある場合、そのデータの一部を日次合計テーブルに結合して、レポートと分析によってより迅速に表示できるようにすることです。システム。このための擬似コードは次のとおりです。

foreach( row in transactions_table )
    if( row in totals_table already exists )
        update totals_table, add my totals to the totals row
    else
        insert into totals_table with my row as the base values
    delete ( or archive ) row

おわかりのように、ループのブロックは比較的簡単に実装でき、カーソル/ループの反復も同様です。ただし、実行時間は非常に遅くて扱いにくく、私の質問は次のとおりです。そのようなタスクを実行するための非反復的な方法はありますか、それともこれは「吸い上げ」てカーソルを使用する必要があるまれな例外の 1 つですか? ?

このトピックについていくつかの議論がありました。いくつかは似ているように見えますが、if/else ステートメントと別のテーブルでの操作のために使用できません。たとえば、次のようになります。

列ベースのロジックで SQL データの行をマージする方法は? この質問は、すべての合計のビューを返すだけであり、実際には別のテーブルへの追加または更新について論理的な決定を下していないため、適用できないようです

SQL ループには、可能と思われるいくつかのケース ステートメントを使用した選択についていくつかのアイデアがあるようですが、別のテーブルのステータスに応じて実行する必要がある 2 つの操作があるため、このソリューションは適合しないようです。

カーソルを使用せずに各行の SQL ストアド プロシージャを呼び出すグループ。

この苛立たしい問題に取り組むためのアドバイスはありますか?

ノート

SQL Server 2008 を使用しています

スキーマのセットアップは次のとおりです。

合計: (id int pk、totals_date date、store_id int fk、machine_id int fk、total_in、total_out)

トランザクション: (transaction_id int pk、transaction_date datetime、store_id int fk、machine_id int fk、transaction_type (IN または OUT)、transaction_amount decimal)

合計は、店舗別、機械別、および日付別に計算する必要があり、すべての IN トランザクションを total_in に合計し、OUT トランザクションを total_out に合計する必要があります。目標は、疑似データ キューブを動作させることです。

4

2 に答える 2

5

これは、2つのセットベースのステートメントで行います。

BEGIN TRANSACTION;

DECLARE @keys TABLE(some_key INT);

UPDATE tot
  SET totals += tx.amount
OUTPUT inserted.some_key -- key values updated
INTO @keys
FROM dbo.totals_table AS tot WITH (UPDLOCK, HOLDLOCK)
INNER JOIN 
(
  SELECT t.some_key, amount = SUM(amount)
  FROM dbo.transactions_table AS t WITH (HOLDLOCK)
  INNER JOIN dbo.totals_table AS tot
  ON t.some_key = tot.some_key
  GROUP BY t.some_key
) AS tx
ON tot.some_key = tx.some_key;

INSERT dbo.totals_table(some_key, amount)
  OUTPUT inserted.some_key INTO @keys
  SELECT some_key, SUM(amount)
  FROM dbo.transactions_table AS tx
  WHERE NOT EXISTS 
  (
    SELECT 1 FROM dbo.totals_table
    WHERE some_key = tx.some_key
  )
  GROUP BY some_key;

DELETE dbo.transactions_table
  WHERE some_key IN (SELECT some_key FROM @keys);

COMMIT TRANSACTION;

(簡潔にするために、エラー処理、適用可能な分離レベル、ロールバック条件などは省略されています。)

最初に更新を行うので、新しい行を挿入してから更新しないようにし、作業を2回実行し、場合によっては2回カウントします。どちらの場合も、一時テーブルへの出力を使用して、txテーブルから行をアーカイブ/削除することができます。

MERGE彼らがこれらのバグのいくつかを解決し、それについて十分に読んで、並行性と追加のヒントのない原子性。回避できる競合状態。あなたができないバグ。

ニコラのコメントからの別の選択肢

CREATE VIEW dbo.TotalsView
WITH SCHEMABINDING
AS
   SELECT some_key_column(s), SUM(amount), COUNT_BIG(*)
    FROM dbo.Transaction_Table
    GROUP BY some_key_column(s);
GO
CREATE UNIQUE CLUSTERED INDEX some_key ON dbo.TotalsView(some_key_column(s));
GO

合計を取得するクエリを作成する場合は、ビューを直接参照するか、クエリとエディションに応じて、ベーステーブルを参照している場合でもビューが自動的に照合される場合があります。

注:Enterprise Editionを使用していない場合は、NOEXPANDヒントを使用して、ビューによって具体化された事前に集計された値を利用する必要がある場合があります。

于 2013-03-12T15:21:56.917 に答える
0

ループは必要ないと思います。

あなたはただすることができます

  • フィルター/グループに一致するすべての行/合計を更新する アーカイブ/以前のものを削除します。
  • フィルターに一致しないすべての行を挿入/グループをアーカイブ/前を削除します。

SQL は、行ごとではなく大量のデータを使用することになっています。

于 2013-03-12T15:23:29.117 に答える