web-dev-qa-db-ja.com

SQLテーブルのバージョン管理-それをどのように処理しますか?

次に、データが入力された架空のシナリオを示します。税務上、架空の会社は履歴データの記録を保持する必要があります。このため、バージョン列を表に含めました。

_TABLE EMPLOYEE: (with personal commentary)

|ID | VERSION | NAME       | Position | PAY |
+---+---------+------------+----------+-----+
| 1 |    1    | John Doe   | Owner    | 100 | Started company
| 1 |    2    | John Doe   | Owner    |  80 | Pay cut to hire a coder
| 2 |    1    | Mark May   | Coder    |  20 | Hire said coder
| 2 |    2    | Mark May   | Coder    |  30 | Productive coder gets raise
| 3 |    1    | Jane Field | Admn Asst|  15 | Need office staff
| 2 |    3    | Mark May   | Coder    |  35 | Productive coder gets raise
| 1 |    3    | John Doe   | Owner    | 120 | Sales = profit for owner!
| 3 |    2    | Jane Field | Admn Asst|  20 | Raise for office staff
| 4 |    1    | Cody Munn  | Coder    |  20 | Hire another coder
| 4 |    2    | Cody Munn  | Coder    |  25 | Give that coder raise
| 3 |    3    | Jane Munn  | Admn Asst|  20 | Jane marries Cody <3
| 2 |    4    | Mark May   | Dev Lead |  40 | Promote mark to Dev Lead
| 4 |    3    | Cody Munn  | Coder    |  30 | Give Cody a raise
| 2 |    5    | Mark May   | Retired  |   0 | Mark retires
| 5 |    1    | Joey Trib  | Dev Lead |  40 | Bring outside help for Dev Lead
| 6 |    1    | Hire Meplz | Coder    |  10 | Hire a cheap coder
| 3 |    4    | Jane Munn  | Retired  |   0 | Jane quits
| 7 |    1    | Work Fofre | Admn Asst|  10 | Hire Janes replacement
| 8 |    1    | Fran Hesky | Coder    |  10 | Hire another coder
| 9 |    1    | Deby Olav  | Coder    |  25 | Hire another coder
| 4 |    4    | Cody Munn  | VP Ops   |  80 | Promote Cody
| 9 |    2    | Deby Olav  | VP Ops   |  80 | Cody fails at VP Ops, promote Deby
| 4 |    5    | Cody Munn  | Retired  |   0 | Cody retires in shame
| 5 |    2    | Joey Trib  | Dev Lead |  50 | Give Joey a raise
+---+---------+------------+----------+-----+
_

さて、「現在のコーダーのリストを取得する」のようなことをしたい場合、_SELECT * FROM EMPLOYEE WHERE Position = 'Coder'_を実行することはできません。これは、大量の履歴データが返されるためです。これは悪いことです。

このシナリオを処理するための良いアイデアを探しています。私にはいくつかのオプションが飛び出してきますが、きっと誰かが「うわー、それはルーキーの間違いだ、グロー...これをサイズで試してみよう」と言うだろうと確信しています。 :-)

アイデア番号1:このような現在のバージョンのバージョンテーブルを保持します

_TABLE EMPLOYEE_VERSION:

|ID |VERSION|
+---+-------+
| 1 |   3   |
| 2 |   5   |
| 3 |   4   |
| 4 |   6   |
| 5 |   2   |
| 6 |   1   |
| 7 |   1   |
| 8 |   1   |
| 9 |   2   |     
+---+-------+
_

単一のクエリでそれをどのように行うかはわかりませんが、それが可能であると確信しているため、少しの労力でそれを理解できると思います。

もちろん、EMPLOYEEテーブルに挿入するたびにこのテーブルを更新して、指定したIDのバージョンをインクリメントする必要があります(または新しいIDが作成されたときにバージョンテーブルに挿入します)。

そのオーバーヘッドは望ましくないようです。

アイデア番号2:アーカイブテーブルとメインテーブルを保持します。メインテーブルを更新する前に、上書きする行をアーカイブテーブルに挿入し、バージョニングについて心配していなかったかのように、通常どおりメインテーブルを使用します。

アイデア番号3:SELECT * FROM EMPLOYEE WHERE Position = 'Coder' and version=MaxVersionForId(EMPLOYEE.ID)...の行に沿って何かを追加するクエリを見つけます。これは私にとって最良の考えのようですが、現時点では本当にわかりません。

アイデア番号4:「current」の列を作成し、「WHERE current = true AND ...」を追加します

確かにこれまでに人々がこれを行ったことがあり、同じ問題にぶつかり、共有するべき洞察を持っているので、私はそれを収集するようになりました! :)私はすでにここで問題の例を見つけようとしましたが、それらは特定のシナリオに特化しているようです。

ありがとう!

編集1:

最初に、私はすべての回答に感謝し、あなたはすべて同じことを言った-DATEは_VERSION NUMBER_より優れている。私が_VERSION NUMBER_を使用した理由の1つは、次のシナリオを防ぐためにサーバーでの更新プロセスを簡略化することでした

人物Aは、セッションに従業員レコード3をロードし、バージョン4を持っています。人物Bは、セッションに従業員レコード3をロードし、バージョン4を持っています。個人Aは、変更を加えてコミットします。これは、データベース内の最新バージョンが4であるため機能します。現在は5です。ユーザーBが変更を行い、コミットします。最新バージョンは5ですが、最新バージョンは4であるため、これは失敗します。

_EFFECTIVE DATE_パターンはこの問題にどのように対処しますか?

編集2:

私は次のような方法でそれを行うことができると思います。Aは彼のセッションで従業員レコード3をロードし、その有効日は2010年1月1日午後1時で、期限はありません。人Bがセッションに従業員レコード3をロードし、その有効日は2010年1月1日午後1時で、期限はありません。人物Aが変更を行い、コミットします。古いコピーは、2010年9月22日午後1時の有効期限でアーカイブテーブル(基本的にはアイデア2)に送られます。メインテーブルの更新されたバージョンの有効日は2010年9月22日午後1時です。人物Bが変更を行い、コミットします。 (データベースとセッションの)有効日が一致しないため、コミットは失敗します。

37
corsiKa

あなたは間違った道を歩み始めたと思います。

通常、履歴データをバージョン管理または保存するには、2つのうちの1つ(または両方)を実行します。

  1. 元のテーブルを模した別のテーブルと、変更された日付の日付/時刻列があります。レコードが更新されるたびに、更新の直前に既存の内容を履歴テーブルに挿入します。

  2. 別の倉庫データベースがあります。この場合、上記の#1と同じようにバージョンを付けることができますORスナップショットは時々(毎時、毎日、毎週)です。

バージョン番号を通常のテーブルと同じテーブルに保持すると、いくつかの問題が発生します。まず、テーブルのサイズが大きくなっていきます。これにより、通常の本番クエリに常にプレッシャーがかかります。

次に、各レコードの最新バージョンが使用されていることを確認するために、結合などのクエリの複雑さを大幅に増やします。

38
NotMe

ここにあるものは、緩やかに変化するディメンション(SCD)と呼ばれます。それを扱うためのいくつかの実証済みの方法があります:

http://en.wikipedia.org/wiki/Slowly_changing_dimension

誰もそれを名前で呼ぶようには思えないので、私はそれを付け加えたいと思いました。

33
Zachary Yates

これが私の推奨するアプローチです。これは、過去に私にとって非常にうまく機能していました。

  • バージョン番号を忘れてください。代わりに、StartDate列とEndDate列を使用してください
  • 同じIDの日付範囲が重複していないこと、および同じNULLEndDateIDを持つレコードが1つだけであることを確認するトリガーを記述します(これは現在有効なレコードです)
  • StartDateおよびEndDateにインデックスを配置します。これにより、適切なパフォーマンスが得られます

これにより、日付で簡単にレポートできます。

select *
from MyTable 
where MyReportDate between StartDate and EndDate

または現在の情報を取得します。

select *
from MyTable 
where EndDate is null
11
RedFilter

最近のデータベース用に設計したアプローチは、次のようにリビジョンを使用することです。

  • エンティティを2つのテーブルに保持します。

    1. 「従業員」には、主キーIDと、バージョン管理を希望しないデータ(存在する場合)が格納されます。

    2. "employee_revision"は、従業員に関するすべての顕著なデータを格納します。従業員テーブルへの外部キーと、 "revision"と呼ばれるテーブルへの外部キー "RevisionID"が含まれます。

  • 「リビジョン」という新しいテーブルを作成します。これは、従業員だけでなく、データベース内のすべてのエンティティで使用できます。これには、主キー(またはオートナンバー、またはデータベースがこのようなものを呼び出すもの)のID列が含まれています。また、EffectiveFrom列とEffectiveTo列も含まれています。また、人間が読みやすいように、プライマリリビジョンテーブル(この場合は「従業員」)の名前を含むテキスト列(entity_type)もテーブルにあります。リビジョンテーブルには外部キーが含まれていません。 EffectiveFromのデフォルト値は1-Jan-1900で、EffectiveToのデフォルト値は31-Dec-9999です。これにより、日付のクエリを簡略化できません。

リビジョンテーブルが(EffectiveFrom、EffectiveTo、RevisionID)および(RevisionID、EffectiveFrom、EffectiveTo)にも適切にインデックス付けされていることを確認します。

次に、結合と単純な<>比較を使用して、任意の日付に適切なレコードを選択できます。これは、エンティティ間の関係も完全にバージョン管理されることを意味します。実際、SQL Serverのテーブル値関数を使用して、任意の日付の非常に単純なクエリを実行できると便利です。

次に例を示します(従業員の名前にバージョンを付けたくないため、従業員が名前を変更した場合、変更は歴史的に有効になります)。

--------
employee
--------
employee_id  |  employee_name
-----------  |  -------------
12351        |  John Smith

-----------------
employee_revision
-----------------
employee_id  |  revision_id  |  department_id  |  position_id  |  pay
-----------  |  -----------  |  -------------  |  -----------  |  ----------
12351        |  657442       |  72             |  23           |  22000.00
12351        |  657512       |  72             |  27           |  22000.00
12351        |  657983       |  72             |  27           |  28000.00

--------
revision
--------
revision_id  |  effective_from  |  effective_to  |  entity_type
-----------  |  --------------  |  ------------  |  -----------
657442       |  01-Jan-1900     |  03-Mar-2007   |  EMPLOYEE
657512       |  04-Mar-2007     |  22-Jun-2009   |  EMPLOYEE
657983       |  23-Jun-2009     |  31-Dec-9999   |  EMPLOYEE

リビジョンメタデータを別のテーブルに保存する利点の1つは、すべてのエンティティに一貫して適用できることです。もう1つは、すべてのテーブルを変更しなくても、ブランチやシナリオなどの他のものが含まれるように拡張する方が簡単なことです。私の主な理由は、メインエンティティテーブルをすっきりと整理するためです。

(上記のデータと例は架空のものです-私のデータベースは従業員をモデル化していません)。

11
James Cane

質問は8年前に行われましたが、SQL Server 2016にはこの機能が正確に存在することを言及する価値があります。システムバージョンの一時テーブル

SQL Server 2016以降のすべてのテーブルには、SQL Server自体によって履歴データが自動的に入力される履歴テーブルを含めることができます。

必要なのは、2つのdatetime2列と1つの句をテーブルに追加することだけです。

_CREATE TABLE Employee 
(
    Id int NOT NULL PRIMARY KEY CLUSTERED,
    [Name] varchar(50) NOT NULL,
    Position varchar(50)  NULL,
    Pay money NULL,
    ValidFrom datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
        PERIOD FOR SYSTEM_TIME (ValidFrom,ValidTo)
)  
WITH (SYSTEM_VERSIONING = ON);
_

システムバージョン対応表は、データの履歴を維持する一時表を作成します。カスタム名を使用できますWITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ) );

このリンク に、システムバージョンのテンポラルテーブルの詳細が記載されています。

@NotMeが述べたように、履歴テーブルは非常に速く成長する可能性があるため、これを回避する方法はいくつかあります。 こちらをご覧ください

3

あなたは間違いなくこれを間違っています。データベースをスムーズに実行し続けるには、必要な本番テーブルに最小限の量のデータのみが含まれている必要があります。必然的に履歴データをライブで保持すると冗長性が追加され、クエリが複雑になり、パフォーマンスが低下します。さらに、DailyWTFに送信する前に、後継者はこれを実際に斜めに見ます。

代わりに、たとえばEmployeeHistoricalなどのテーブルのコピーを作成しますが、ID列をIDとして設定しません(新しいID列とdateCreatedタイムスタンプ列を追加することもできます)。次に、更新と削除時に起動し、完全な行のコピーを履歴テーブルに書き込むトリガーをEmployeeテーブルに追加します。また、編集中にユーザーのIDを取得することは、監査の目的でしばしば役立ちます。

一般的に私がアクティブなテーブルでこれをしているとき、私は別のデータベースで履歴テーブルを作成してみます。これにより、プライムデータベースの断片化(したがってメンテナンス)が減り、バックアップの処理が容易になります。アーカイブは非常に大きくなる可能性があるためです。大。

編集の競合に関する問題は、通常のデータベーストランザクションとロックメカニズムで処理する必要があります。そのような自分をエミュレートするためにアドホックハックアップをコーディングすると、常に時間がかかり、エラーが発生しやすくなります(常に考えられなかった一部のEdge条件がポップアップし、ロックを正しく書き込むためには、実際に試してみる必要があります sempahores 、それは明らかに自明ではありません)

2
Cruachan

アイデア3が機能します。

SELECT * FROM EMPLOYEE AS e1
WHERE Position = 'Coder'
AND Version = (
    SELECT MAX(Version) FROM Employee AS e2
    WHERE e1.ID=e2.ID)

ただし、プログラミングや追跡がはるかに簡単な日付のようなものを実際に使用したい場合は、同じロジックを使用します(EffectiveDate列のようなもの)

[〜#〜]編集[〜#〜]

クリスは、特に頻繁な更新が予想される場合は、パフォーマンスのためにこの情報をプロダクションテーブルから移動することについて完全に正しいです。別のオプションは[〜#〜] view [〜#〜]を作成することです。これは、このテーブルから作成した各個人の情報の最新バージョンのみを表示します。

2
JNK