これがあなたの述べた要件を達成するためのモデルです。
IDEF1X表記へのリンク リレーショナルモデリング標準に慣れていない人のために。
-
5NFに正規化。重複する列はありません。更新の異常やヌルはありません。
-
製品のステータスが変更されたら、現在のDateTimeを使用してProductStatusに行を挿入するだけです。前の行に触れる必要はありません(これは真実であり、真実のままです)。 (アプリ以外の)ツールを報告するダミー値を解釈する必要はありません。
-
DateTimeは、製品がそのステータスに置かれた実際のDateTimeです。必要に応じて、「From」。 「宛先」は簡単に導き出すことができます。これは、製品の次の(DateTime>「From」)行のDateTimeです。存在しない場合、値は現在の日時です(ISNULLを使用)。
最初のモデルが完成しました。 (ProductId、DateTime)は、主キーに一意性を提供するのに十分です。ただし、特定のクエリ条件の速度を要求するため、物理レベルでモデルを拡張し、以下を提供できます。
-
インデックス(すでにPKインデックスがあるので、2番目のインデックスを追加する前に最初に拡張します)は、対象となるクエリ({ProductId | DateTime | Status}の任意の配置に基づくクエリは、データ行に移動します)。これにより、Status ::ProductStatusの関係が非識別(破線)から識別タイプ(実線)に変更されます。
-
PK配置は、Product⇢DateTime⇢Statusに基づいて、ほとんどのクエリが時系列になることに基づいて選択されます。
-
2番目のインデックスは、ステータスに基づくクエリの速度を向上させるために提供されています。
-
代替配置では、それは逆になります。つまり、ほとんどの場合、すべての製品の現在のステータスが必要です。
-
ProductStatusのすべてのレンディションで、セカンダリインデックス(PKではない)のDateTime列はDESCendingです。最新のものが最初です。
私はあなたが要求した議論を提供しました。もちろん、妥当なサイズのデータセットを試して、独自の決定を行う必要があります。ここにわからないことがありましたら、お問い合わせください。拡大させていただきます。
コメントへの回答
現在の状態が2のすべての製品を報告する
SELECT ProductId,
Description
FROM Product p,
ProductStatus ps
WHERE p.ProductId = ps.ProductId -- Join
AND StatusCode = 2 -- Request
AND DateTime = ( -- Current Status on the left ...
SELECT MAX(DateTime) -- Current Status row for outer Product
FROM ProductStatus ps_inner
WHERE p.ProductId = ps_inner.ProductId
)
-
ProductId
インデックス付き、先頭の列、両側 -
DateTime
インデックス付き、対象クエリオプションの2番目の列 -
StatusCode
インデックス付き、対象クエリオプションの3列目 -
StatusCode
以降 インデックスが降順である場合、内部クエリを満たすために必要なフェッチは1つだけです -
1つのクエリに対して、行が同時に必要です。それらは互いに接近しています(Clstered Indexによる)。行サイズが短いため、ほとんどの場合、同じページに表示されます。
これは通常のSQL、サブクエリであり、SQLエンジンのパワーであるリレーショナルセット処理を使用します。これは1つの正しい方法です 、これ以上速いものはなく、他の方法は遅くなります。どのレポートツールでも、数回クリックするだけで、入力せずにこのコードを生成できます。
ProductStatusの2つの日付
DateTimeFromやDateTimeToなどの列は重大なエラーです。重要度の高い順に見ていきましょう。
-
これは重大な正規化エラーです。 「DateTimeTo」は、次の行の単一のDateTimeから簡単に導出できます。したがって、冗長であり、列が重複しています。
- 精度は考慮されていません。これは、DataType(DATE、DATETIME、SMALLDATETIME)によって簡単に解決されます。 1秒未満、マイクロ秒、またはナノ秒のいずれを表示するかは、ビジネス上の決定事項です。保存されているデータとは何の関係もありません。
-
DateTo列の実装は、(次の行のDateTimeと)100%重複しています。これにはディスク容量の2倍かかります 。大きなテーブルの場合、それはかなりの不要な無駄になります。
-
短い行であるため、論理的および物理的なI/Oの2倍が必要になります。 アクセスするたびに、表を読みます。
-
そして2倍のキャッシュスペース (言い換えると、特定のキャッシュスペースに収まる行数は半分になります)。
-
重複する列を導入することにより、エラーの可能性が導入されました(値は、重複するDateTimeTo列または次の行のDateTimeFromから2つの方法で導出できるようになりました)。
-
これは更新の異常でもあります 。 DateTimeFrom Updatedを更新するときは、前の行のDateTimeToをフェッチして(近くにあるので大したことはありません)、更新する必要があります(回避できる追加の動詞なので大したことです)。
-
「短い」と「コーディングショートカット」は関係ありません。SQLは面倒なデータ操作言語ですが、SQLだけがあります (それに対処するだけです)。サブクエリをコーディングできない人は、実際にはコーディングしないでください。マイナーなコーディングの「難しさ」を緩和するために列を複製する人は、実際にはデータベースをモデリングするべきではありません。
最高次のルール(正規化)が維持されている場合は、低次の問題のセット全体が排除されることに注意してください。
セットの観点から考える
-
単純なSQLを作成するときに「困難」を抱えたり、「苦痛」を経験したりする人は、職務を遂行する上で不自由になります。通常、開発者はそうではありません セットの観点から考える リレーショナルデータベースはセット指向モデルです 。
-
上記のクエリでは、CurrentDateTimeが必要です。 ProductStatusはセットであるため 時系列の製品状態の場合、セットの最新またはMAX(DateTime)が必要です。 製品に属します。
-
次に、セットの観点から、「難しい」とされるものを見てみましょう。 。各商品が特定の状態にあった期間のレポートの場合:DateTimeFromは使用可能な列であり、水平方向のカットオフ、サブセットセットを定義します。 (以前の行を除外できます); DateTimeToは、サブセットセットの最も早いものです。 製品の状態の。
SELECT ProductId,
Description,
[DateFrom] = DateTime,
[DateTo] = (
SELECT MIN(DateTime) -- earliest in subset
FROM ProductStatus ps_inner
WHERE p.ProductId = ps_inner.ProductId -- our Product
AND ps_inner.DateTime > ps.DateTime -- defines subset, cutoff
)
FROM Product p,
ProductStatus ps
WHERE p.ProductId = ps.ProductId
AND StatusCode = 2 -- Request
-
次の行を取得するという観点から考える 行指向であり、ではありません セット指向の処理。セット指向のデータベースで作業する場合の不自由。 Optimiserにすべてのことを考えさせてください。 SHOWPLANを確認してください。これにより、美しく最適化されます。
-
セットで考えることができない したがって、単一レベルのクエリのみの記述に限定されるため、次の理由は合理的ではありません。データベースに大規模な複製と更新異常を実装する。オンラインリソースとディスクスペースを浪費する。半分のパフォーマンスを保証します。簡単に導出できるデータを取得するための単純なSQLサブクエリの記述方法を学ぶ方がはるかに安価です。