web-dev-qa-db-ja.com

データベースのリレーショナルモデルが重要なのはなぜですか?

上司とデータベースを実装する必要があるプロジェクトに近づいています。私たちは非常に小さな新興企業なので、作業環境は非常に個人的なものです。

彼は以前に私に会社のデータベースの1つを与えており、RDBMSの学校で私が教えられた(そして読んだ)ものとは完全に反対しました。たとえば、ここには(独立したデータベースごとに)1つのテーブルで構成されるデータベース全体があります。それらのテーブルの1つは20列以上の長さであり、コンテキストのために、ここにoneテーブルの列名の一部を示します。

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

重要なのは、エンティティデータ(名前、サイズ、購入日など)を保持する個別のテーブルが必要な場合に、データベースごとに1つの大きなテーブルにすべてを表示することです。

この設計を改善したいのですが、適切に正規化およびセグメント化されたデータモデルが実際にこの製品を改善する理由がわかりません。私は大学のデータベース設計に精通していて、howを理解していますが、whyこれは実際にデータベースを改善します。

優れたリレーショナルスキーマはなぜデータベースを改善するのですか?

61
8protons

通常、パフォーマンスの引数は最も直感的なものです。特に、正しく正規化されていないデータベースに適切なインデックスを追加することがどのように難しいかを指摘したいと思います(注:非正規化が実際にimproveパフォーマンスになるエッジケースがありますが、両方とも経験がない場合リレーショナルデータベースでは、これらのケースを簡単に確認することはできません)。

もう1つは、ストレージサイズの引数です。冗長性の多い非正規化テーブルでは、はるかに多くのストレージが必要になります。これはパフォーマンスの側面にも影響します。データが多いほど、クエリは遅くなります。

理解が少し難しい議論もありますが、実際には、より多くのハードウェアを投入して解決することができないため、より重要です。それがデータの整合性の問題です。適切に正規化されたデータベースは、特定のIDを持つ製品が常に同じ名前を持つことに注意してください。しかし、非正規化されたデータベースでは、このような不整合が発生する可能性があるため、不整合を回避するために特別な注意を払う必要があります。これにより、プログラミングに時間がかかり、バグが発生して顧客満足度が低下します。

70
Philipp

上司と一緒にデータベースを実装する必要があります...

専用のデータベース管理softwareを使用すると、かなり簡単になる場合があります(申し訳ありませんが、抵抗できませんでした)。

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

このデータベースが、どこで、いつ、誰によって、どの製品が販売されたかを「ログに記録する」ことだけに関心がある場合、可能性がある「OKデータベース」の定義をカバーするのに十分なだけ拡張することができます。このデータがanything elseに使用されている場合、それは本当にかなり貧弱です。

だが ...

このデータを使用するアプリケーション/クエリの応答は遅くなりますか?そうでない場合、解決するべき実際の問題はありません。確かに、見た目も見た目も醜いですが、worksの場合、「良い」と言った「ポイント」が得られないでしょう。

不十分なデータモデリングが原因であると思われる明確な症状(つまり問題)を見つけた場合は、より良いソリューションのプロトタイプを作成してください。これらの「データベース」のいずれかのコピーを取り、データを正規化して、ソリューションのパフォーマンスが向上するかどうかを確認します。それがかなりより良い場合(そして、私はanyこのデータの更新操作が大幅に改善されることを完全に期待します)、上司に戻って、それらに改善を示す。

..よく..ビューを使用して、データの彼の「単一テーブルビュー」を再作成することは完全に可能です。

24
Phill W.

優れたリレーショナルスキーマはなぜデータベースを改善するのですか?

答えは、データベースが常に改善されないことです。あなたはおそらく教えられたことは 第3正規形 と呼ばれていることに注意する必要があります。

他のフォームは、状況に応じて有効です。これは、質問に答えるための鍵となります。あなたの例は 第1正規形 のようになります。これが現在の状態をよりよく理解するのに役立つ場合。

3NFルールは、データベースを「改善」するデータ間の関係を確立します。

  1. 無効なデータがシステムに入力されないようにします(関係が1対1の場合、その上にコードが記述されていてもエラーが発生します)。データがデータベース内で一貫している場合、データベースの外部で不整合が発生する可能性は低くなります。

  2. コードを検証する方法を提供します(たとえば、多対1の関係は、オブジェクトのプロパティ/動作を制限するシグナルです)。データベースを使用するコードを作成するとき、プログラマーは、コードがどのように機能するかを示すものとしてデータ構造に気付く場合があります。または、データベースがコードと一致しない場合に役立つフィードバックを提供できます。 (残念ながら、これは希望的思考に似ています。)

  3. データベース構築時のミスを大幅に減らすのに役立つルールを提供します。これにより、データベースの存続期間中に随時発生する可能性のある任意の要件に基づいて構築しないようにします。代わりに、特定の目標を達成するために情報を体系的に評価します。

  4. 適切なデータベース構造は、データストレージを最小化し、データを取得するためのストレージ呼び出しを最小化し、メモリ内リソースを最大化し、特定のデータセットのデータの並べ替え/操作を最小化する方法でデータを接続することにより、パフォーマンスを改善します。それに対して実行します。しかし、「適切な」構造は、データの量、データの性質、クエリのタイプ、システムリソースなどによって異なります。正規化すると、パフォーマンスが低下する可能性があります(つまり、すべてのデータを1つのテーブルとしてロードすると、結合が遅くなる可能性があります)クエリ)。トランザクション処理(OLTP)とビジネスインテリジェンス(データウェアハウス)は大きく異なります。

小さなデータセットを持つ小さな会社では、現在の状態に問題がないことに気付くでしょう。ただし、テーブルが大きくなると、それを使用するシステムの速度が低下する可能性があるため、大きくなると、後で「修正」するのが面倒になります。

通常、会社が成長するにつれて、高速トランザクションを強調する必要があります。ただし、会社がより緊急に必要とする可能性のある他のことではなく、今このプロジェクトに時間を費やしている場合、会社が実際に成長することはないため、問題が発生することはありません。それが「事前最適化の課題」です。今、貴重な時間を費やす場所です。

幸運を!

14
Jim

1つの大きな「神テーブル」を使用することが悪い理由はいくつかあります。作成したサンプルデータベースを使用して、問題を説明します。スポーツイベントをモデル化しようとしているとしましょう。ゲームとそれらのゲームでプレイするチームをモデル化したいとします。複数のテーブルを持つデザインは次のようになります(これは意図的に非常に単純化されているため、より正規化を適用できる場所に巻き込まれないようにしてください)。

Teams
Id | Name | HomeCity

Games
Id | StartsAt | HomeTeamId | AwayTeamId | Location

単一のテーブルデータベースは次のようになります

TeamsAndGames
Id | TeamName | TeamHomeCity | GameStartsAt | GameHomeTeamId | GameAwayTeamId | Location

まず、これらのテーブルのインデックスを作成する方法を見てみましょう。チームのホームシティのインデックスが必要な場合は、TeamsテーブルまたはTeamsAndGamesテーブルに簡単に追加できます。インデックスを作成するときはいつでも、ディスクのどこかに保存し、テーブルに行が追加されるときに更新する必要があることに注意してください。 Teamsテーブルの場合、これは非常に簡単です。新しいチームを配置すると、データベースがインデックスを更新します。しかし、TeamsAndGamesはどうですか?まあ、同じことがTeamsの例からも当てはまります。チームを追加すると、インデックスが更新されます。しかし、ゲームを追加したときにも発生します。ゲームの場合、そのフィールドはnullになりますが、いずれにしても、そのゲームのインデックスを更新してディスクに保存する必要があります。 1つのインデックスでは、これは悪くないように聞こえます。ただし、このテーブルに詰め込まれた複数のエンティティに多くのインデックスが必要な場合、インデックスを格納するための多くのスペースと、インデックスが適用されないもののためにインデックスを更新するために多くのプロセッサ時間を浪費します。

第二に、データの整合性。 2つの個別のテーブルを使用する場合、GamesテーブルからTeamsテーブルへの外部キーを使用して、ゲームでプレイしているチームを定義できます。そして、HomeTeamId列とAwayTeamId列をnullにできないようにすると、データベースは、入れたすべてのゲームに2つのチームがあり、それらのチームがデータベースに存在することを保証します。しかし、単一テーブルのシナリオはどうですか?まあ、このテーブルには複数のエンティティがあるので、それらの列はnullにできるようにする必要があります(nullにできないようにして、ガベージデータをそこに押し込むこともできますが、それは恐ろしい考えです)。これらの列がnull可能である場合、ゲームを挿入したときに2つのチームがあることをデータベースが保証できなくなります。

しかし、とにかくそれのために行くことにした場合はどうなりますか?これらのフィールドが同じテーブル内の別のエンティティを指すように、外部キーを設定します。しかし、データベースは、これらのエンティティが正しいタイプであることではなく、テーブルに存在することを確認するだけです。非常に簡単にGameHomeTeamIdを別のゲームのIDに設定することができ、データベースはまったく文句を言うことはありません。複数テーブルのシナリオでそれを試した場合、データベースは適合をスローします。

「まあ、私たちはコードでそれを決してしないことを確認するだけです」と言って、これらの問題を緩和しようとすることができます。初めてバグのないコードを書く能力に自信があり、ユーザーが試みる可能性のあるすべての奇妙な組み合わせを考慮に入れる能力がある場合は、すぐに進んでください。私は個人的には、これらのいずれかを実行する能力に自信がないので、データベースに追加のセーフティネットを提供します。

(これは、外部キーを使用するのではなく、行間ですべての関連データをコピーする設計の場合、さらに悪化します。スペルやその他のデータの不整合は解決が困難になります。「ジョン」が「ジョン」のスペルミスであるかどうかはどうすればわかりますか「またはそれが意図的だった場合(彼らは2人の別々の人だから)?)

3番目に、ほとんどすべての列はnull可能であるか、コピーされたデータまたはガベージデータで埋められる必要があります。ゲームにはTeamNameまたはTeamHomeCityは必要ありません。したがって、すべてのゲームには、そこにある種のプレースホルダーが必要か、またはnull可能である必要があります。そしてそれがnullableであるならば、データベースはTeamNameのないゲームを喜んで引き受けます。また、ビジネスロジックでそのようなことは絶対に起こらないと言っていても、名前のないチームが必要です。

別のテーブルが必要になる理由は他にもいくつかあります(開発者の健全性の維持を含む)。テーブルが大きいほうがよい理由はいくつかあります(非正規化によってパフォーマンスが向上する場合があります)。これらのシナリオはほとんどありません(通常、パフォーマンスメトリックがあり、それが実際に問題であり、インデックスの欠落などではないことを示す場合に最適です)。

最後に、保守しやすいものを開発します。 「機能する」からといって、問題がないという意味ではありません。 (神のクラスのような)神のテーブルを維持しようとすることは悪夢です。あとは苦痛を覚悟しているだけです。

11
Becuzz

今日の引用:「理論と実践は同じであるはずです...理論的には

非正規化テーブル

一意のすべて保持テーブルに冗長データが含まれていると、1つの利点があります。結合を行う必要がないため、その行に関するレポートのコーディングが非常に簡単になり、実行が高速になります。しかし、これは高コストです:

  • リレーションの重複コピーを保持します(例:IngCompanyIDおよびvrCompanyName)。マスターデータを更新するには、正規化されたスキーマよりも多くの行を更新する必要がある場合があります。
  • それはすべてを混ぜ合わせます。データベースレベルでの簡単なアクセス制御を保証することはできません。ユーザーAが会社情報のみを、ユーザーBが製品情報のみを更新できることを確認します。
  • データベースレベルで整合性ルールを保証することはできません(たとえば、会社IDの会社名が1つだけであることを強制する主キー)。
  • 正規化されたテーブルのサイズといくつかのインデックスの統計を利用して、複雑なクエリに最適なアクセス戦略を特定できるDBオプティマイザーのメリットは十分にありません。これにより、結合を回避するという限られた利点がすぐに相殺される可能性があります。

正規化されたテーブル

上記の欠点は、正規化されたスキーマの利点です。もちろん、クエリを書くのは少し複雑かもしれません。

つまり、正規化されたスキーマ表現する構造とデータ間の関係がはるかに良くなります。私は挑発的であり、注文されたオフィスの引き出しのセットを使用するために必要な規律とゴミ箱の使いやすさの間の違いと同じ種類の違いだと言います。

6
Christophe

私はあなたの質問には少なくとも2つの部分があると思います:

1。異なるタイプのエンティティを同じテーブルに格納しないのはなぜですか?

ここで最も重要な答えは、コードの可読性と速度です。 SELECT name FROM companies WHERE id = ?SELECT companyName FROM masterTable WHERE companyId = ?よりもはるかに読みやすく、誤って無意味なクエリを実行する可能性が低くなります(たとえば、会社と従業員が異なるテーブルに格納されている場合、SELECT companyName FROM masterTable WHERE employeeId = ?は不可能です)。 。速度に関しては、データベーステーブルからのデータは、テーブル全体を順番に読み取るか、インデックスから読み取ることによって取得されます。テーブル/インデックスに含まれるデータが少ない場合はどちらも高速であり、データが異なるテーブルに格納されている場合(そして、テーブル/インデックスの1つのみを読み取る必要がある場合)はそうです。

2。単一のタイプのエンティティを、異なるテーブルに格納されているサブエンティティに分割する必要があるのはなぜですか?

ここでの理由は、主にデータの不整合を防ぐためです。シングルテーブルアプローチでは、注文管理システムの場合、顧客が注文した製品の顧客名、顧客住所、および製品IDを単一のエンティティとして保存できます。顧客が複数の製品を注文した場合、データベースには顧客の名前と住所の複数のインスタンスがあります。最良のケースでは、データベースのデータが重複しているため、少し遅くなる可能性があります。しかしさらに悪いのは、データが入力されたときに誰か(またはコード)がミスを犯し、企業がデータベース内の異なるアドレスになってしまうことです。これだけで十分です。しかし、会社の名前に基づいて会社の住所を照会すると(例:SELECT companyAddress FROM orders WHERE companyName = ? LIMIT 1)、2つの住所のいずれかが勝手に返され、不整合があったことにさえ気付かないでしょう。ただし、クエリを実行するたびに、DBMSによってクエリが内部的に解決される方法に応じて、実際には異なるアドレスが取得される場合があります。これによりアプリケーションがどこかで壊れる可能性が高く、その破損の根本的な原因を見つけるのは非常に困難です。

マルチテーブルアプローチを使用すると、会社名から会社の住所への機能的な依存関係があることがわかります(会社が1つの住所しか持てない場合) )、(companyName、companyAddress)タプルを1つのテーブル(たとえばcompany)に格納し、(productId、companyName)タプルを別のテーブル(たとえばorder)に格納します。次に、UNIQUEテーブルのcompany制約により、各企業のデータベース内のアドレスが1つだけになるように強制できるため、企業アドレスの不整合が発生することはありません。

注:実際には、パフォーマンス上の理由から、companyNameを直接使用する代わりに、会社ごとに一意のcompanyIdを生成して外部キーとして使用することになるでしょう。ただし、一般的なアプローチは変わりません。

5
Dreamer

TL; DR-彼らは彼らがにいたときにどのように教えられたかに基づいてデータベースを設計しています学校。

私はこの質問を10年前に書いたでしょう。私の前任者が彼らがしたように彼らのデータベースを設計した理由を理解するのに少し時間がかかりました。あなたは次のいずれかで誰かと働いています:

  1. Excelをデータベースとして使用して、データベース設計スキルのほとんどを獲得した、または
  2. 彼らは学校を卒業したときからのベストプラクティスを使用しています。

テーブルに実際にID番号があるので、それが#1であるとは思わないので、#2と仮定します。

学校を卒業した後、 AS/4 (別名IBM i)を使用するショップで働いていました。私は、彼らがデータベースを設計する方法にいくつか奇妙なことを見つけ、私がデータベースの設計方法を教えられた方法に従うように変更を加えることを提唱し始めました。 (当時私は馬鹿でした)

物事がそのように行われた理由を説明するのに、辛抱強い年配のプログラマが必要でした。彼らがスキーマを変更していなかったのは、それよりも古いプログラムが壊れてしまうからでした。文字通り、あるプログラムのソースコードには、私が生まれる前の年の作成日がありました。私たちが取り組んでいたシステムでは、 それらのプログラム は、データベースのクエリプランナーが処理するすべてのロジックと操作を実装する必要がありました。 (クエリの1つでEXPLAINを実行すると確認できます)

彼は私が実装しようとしたテクニックについては最新でしたが、システムが稼働し続けることは、「教えられたことに反するため」変更を加えることよりも重要でした。私たちのどちらかが始めたすべての新しいプロジェクトは、私たちができるリレーショナルモデルを最大限に活用しました。 残念ながら、その時代の他のプログラマ/コンサルタントは、あたかもそのシステムの以前の制約で作業しているかのようにデータベースを設計しました。


リレーショナルモデルに適合しない、私が遭遇したいくつかの例:

  • 日付は ユリウス日番号 として保存され、実際の日付を取得するには日付テーブルに結合する必要がありました。
  • 同じタイプの連続する列を持つ非正規化テーブル(例:code1,code2, ..., code20
  • 長さMのN個の文字列の配列を表す長さNxMのCHAR列。

これらの設計決定について私に与えられた理由はすべて、データベースが最初に設計されたときのシステムの制約に基づいていました。

Dates-日付関数を使用すると、日付を処理するよりも日付関数(月、日、または平日)を使用する方が、そのすべての情報を含む可能なすべての日付の表。

同じタイプのシーケンシャル列-それらが含まれていたプログラミング環境では、プログラムは行の一部に配列変数を作成できました。そして、それは読み取り操作の数を減らすより簡単な方法でした。

NxM長さのCHAR列-ファイルの読み取り操作を減らすために、構成値を1つの列に移動する方が簡単でした。

彼らが持っていたプログラミング環境を反映するCでの不十分に考えられた例:

#define COURSE_LENGTH 4
#define NUM_COURSES 4
#define PERIOD_LENGTH 2

struct mytable {
    int id;
    char periodNames[NUM_COURSES * PERIOD_LENGTH];  // NxM CHAR Column
    char course1[COURSE_LENGTH];
    char course2[COURSE_LENGTH];
    char course3[COURSE_LENGTH];
    char course4[COURSE_LENGTH];
};

...

// Example row
struct mytable row = {.id= 1, .periodNames="HRP1P2P8", .course1="MATH", .course2="ENGL", .course3 = "SCI ", .course4 = "READ"};

char *courses; // Pointer used to access the sequential columns
courses = (char *)&row.course1;


for(int i = 0; i < NUM_COURSES; i++) {

    printf("%d: %.*s -> %.*s\n",i+1, PERIOD_LENGTH, &row.periodNames[PERIOD_LENGTH * i], COURSE_LENGTH,&courses[COURSE_LENGTH*i]);
}

アウトプット

1:人事->数学
2:P1-> ENGL
3:P2-> SCI
4:P8->読み取り

私が言われたことによると、これのいくつかは当時ベストプラクティスと考えられていました。

3
Core.B