データベース設計:複合キーと 1 列の主キー
-
13-09-2019 - |
質問
私が作業している Web アプリケーションで予期しない「バグ」が発生しました。アプリのデータベースには、(他の多くのテーブルの中でも特に) 「States」 と 「Cities」 と呼ばれる 2 つのテーブルがあります。
'州' テーブルのフィールド:
-------------------------------------------
idStates | State | Lat | Long
-------------------------------------------
'idStates' は自動インクリメントされる主キーです。
'都市' テーブルのフィールド:
----------------------------------------------------------
idAreaCode | idStates | City | Lat | Long
----------------------------------------------------------
'idエリアコード' は、国番号 + 市外局番で構成される主キーです (例:91422 ここで、91 はインドの国番号、422 はインドの都市の市外局番です)。'idStates' は ' から派生した外部キーです州' 内の各都市を関連付けるテーブル都市' テーブルとそれに対応する状態。
私たちは、国番号と市外局番の組み合わせは都市ごとに一意であるため、主キーとして安全に使用できると考えました。すべてがうまくいきました。しかし、インドのある場所でデータベースの設計に予期せぬ「欠陥」が見つかりました。インドは、米国と同様に連邦制民主主義国家であり、地理的に多くの州または連邦直轄領に分かれています。州と連邦直轄領のデータは両方とも「」に保存されます。州' テーブル。ただし、場所は 1 か所あります - チャンディーガル - 2 つの州に属します (ハリヤナ州 そして パンジャーブ)そしてそれ自体が連合の領土でもあります。
明らかに、現在のデータベースの設計では、都市の複数のレコードを保存することはできません。チャンディーガル'.
提案される解決策の 1 つは、列を組み合わせた主キーを作成することです。idエリアコード' そして 'idStates'.
これが可能な限り最善の解決策であるかどうか知りたいのですが?
(ご参考までに:MySQL と InnoDB エンジンを使用しています)。
詳しくは:
- データベースには都市ごとの気象情報が格納されています。したがって、州と都市が各クエリの開始点になります。
- CSV ファイルを使用して、各都市の最新データが毎日挿入されます。CSV ファイルには、各レコードを識別するために使用される idStates (州) 列と idAreaCode (都市) 列が含まれています。
- データベースの正規化は私たちにとって重要です。
注記:city テーブルに自動増分主キーを使用しない理由は、データベースが CSV ファイル (別のアプリによって生成される) を使用して毎日または毎時間更新されるためです。CSV ファイル内の各レコードは、idStates 列と idAreaCode 列によって識別されます。したがって、テーブルが削除されて再度更新された場合でも、都市テーブルで使用される主キーはすべての都市で同じであることが望ましいです。郵便番号 (または PIN コード) と市外局番 (または STD コード) は、一意で静的である (頻繁に変更されない) という基準を満たしており、これらのリストは簡単に入手できます。(インドでは PIN コードを新しい形式に更新中であるため、現時点では市外局番を決定しました)。
の 解決 私たちは、データベース設計を変更するのではなく、アプリケーション レベルでこれを処理することにしました。データベースには「Chandigarh」の 1 つのレコードのみが保存されます。アプリケーションでは、「パンジャブ州チャンディガル」または「ハリヤナ州チャンディガル」の検索用のフラグを作成し、検索をこのレコードにリダイレクトしました。はい、これは理想的ではありませんが、これがこれまでに遭遇した唯一の例外であるため、許容できる妥協点です。
解決
電話帳のデータを集めているようです。あなたは?あなたにとって州はなぜ重要ですか?おそらく、この質問に対する答えによって、どのデータベース設計が最適であるかが決まります。
都市が何であるかは明らかだと思うかもしれません。そうではありません。それはデータをどうするかによって異なります。アメリカにはMSA(Metropolitan Statistical Area)という単位があります。カンザスシティ MSA は、カンザス州カンザスシティとミズーリ州カンザスシティの両方にまたがっています。MSA 単位が意味があるかどうかは、データの使用目的によって異なります。米国の市外局番を使用して都市を決定すると、MSA とはまったく異なるグループ化が行われることになります。繰り返しますが、それはデータをどう扱うかによって異なります。
一般に、政治的部門の階層パターンが崩れた場合、最も一般的な解決策は、多対多の関係を考慮することです。この問題は、他の多対多の問題を解決するのと同じ方法で解決します。2 つの外部キーを持つ新しいテーブルを作成します。この場合、外部キーは IdAreacode と IdStates です。
これで、1 つのエリアコードを複数の州に設定したり、1 つの州を複数の市外局番にまたがったりすることができます。たった 1 つの例外をカバーするためにこの余分なオーバーヘッドを受け入れるのは残念に思えます。あなたが発見した例外は氷山の一角にすぎず、そのような例外はたくさんあるのかどうか知っていますか?
他のヒント
参照テーブルには主キーが持つすべての列が必要になるため、そのテーブルを参照する場合、複合キーを持つと問題が発生する可能性があります。
その場合は、シーケンス主キーを使用し、UNIQUE NOT NULL グループで idAreaCode と idStates を定義するとよいでしょう。
別のテーブル、国を追加するのが最善だと思います。あなたの問題は、データベースの正規化が重要である理由の一例です。異なるキーを 1 つの列に単に組み合わせて一致させることはできません。
したがって、次のテーブルを作成することをお勧めします。
国:
+------------+--------------+ | 国 ID | country_name | +------------+--------------+
状態:
+------------+----------+------------+ | 国 ID | 状態ID | state_name | +------------+----------+------------+
都市
+------------+----------+---------+-----------+ | 国 ID | 状態ID | 都市ID | city_name | +------------+----------+---------+-----------+
データ
+------------+----------+---------+---------+----------+ | 国 ID | 状態ID | 都市ID | データID | your_CSV | +------------+----------+---------+---------+----------+
太字のフィールドは主キーです。米国の場合は 1、インドの場合は 91 など、標準の country_id を入力します。city_id も標準 ID を使用する必要があります。
そうすれば、最小限のオーバーヘッドで、互いに属しているものを非常に高速に見つけることができます。その後、すべてのデータをデータ テーブルに直接入力できるため、1 つのエントリ ポイントとして機能し、すべてのデータを 1 つのスポットに保存できます。mysql についてはわかりませんが、データベースがパーティショニングをサポートしている場合は、country_id または country_id+state_id に従ってデータ テーブルをいくつかのサーバー配列にパーティショニングでき、データベースのパフォーマンスも大幅に向上します。1 番目、2 番目、および 3 番目のテーブルはサーバー負荷にまったく影響せず、参照としてのみ機能します。主に 4 番目のデータ テーブルに取り組みます。重複することなく、必要なだけデータを追加できます。
都市ごとに 1 つのデータしかない場合は、次のようにデータ テーブルを省略して CSV_data を都市テーブルに移動できます。
都市
+------------+----------+---------+-----------+----------+ | 国 ID | 状態ID | 都市ID | city_name | CSV_data | +------------+----------+---------+-----------+----------+
特定の都市のレコードを追加できるようにキーに列を追加すると、データが適切に正規化されません。都市が複数の州のメンバーになれることが分かったとすると、Cities テーブルから州への参照をすべて削除し、州と都市を関連付けることができる StateCity テーブルを追加することをお勧めします (m:m を作成する)関係)。
代理キーを導入します。市外局番が変更されたり、分割されたりした場合はどうしますか?ビジネス キーを主キーとして使用することは、ほとんどの場合間違いです。
上記の要約は、その理由のもう 1 つの例です。
「国番号と市外局番の組み合わせは都市ごとに一意であるため、主キーとして安全に使用できると考えました。」
これを読んだ後、このトピックの内容をさらに読むのをやめました。どうやってこのように理解できるでしょうか?
定義による市外局番 (インターネットで最初に見つけた市外局番):
- 「市外局番は、北米の番号計画に基づいて地理的地域を識別するために使用されるプレフィックス番号です。この 3 桁の番号は、カナダ、米国、メキシコ、ラテンアメリカ、カリブ海諸国を含む北米の任意の番号に割り当てることができます。」[1]
市外局番は変更可能であり、北米でのみ定義されているということはさておき、他の一部の国では市外局番が 3 桁ではありません (国によっては数十万の拠点があるため、3 桁では十分ではありません)。ところで、私の母の市外局番は 5 桁で、固定された地理的位置に厳密に関連付けられているわけではありません。
市外局番には、氷が漂う北極のキャンプ、遊牧民の部族、移動中の軍事部隊、さらには大型の海洋船など、移動する場所が含まれています。
では、いくつかの都市を 1 つに合併する (またはその逆) のはどうでしょうか?
[1]
http://www.successoffice.com/articles/answering-service-glossary-area-code.htm
単純に自動増分される新しい主キー フィールドを Cities テーブルに追加することをお勧めします。KISS 方法論 (シンプルにする)。
私の意見では、他の解決策はどれも面倒で混乱を招きます。
データベースは正規化されていません。部分的に正規化されている可能性があります。その結果、拡張性においてさらに多くのバグや制限が見つかることになります。
Country、State、City の階層構造は問題ありません。一部の人が示唆しているように、多対多の追加テーブルは必要ありません。上記の都市(そしてアメリカの多くの都市)は 3 つの州に複数あります。
CountryCode と AreaCode を 1 つの列に連結して配置すると、アクセスごとにコードが追加されるだけでなく、基本的なデータベース ルールも破られてしまいます。さらに、 CountryCode は正規化されていません。
問題は、 CountryCode+AreaCode が都市のキーとして不適切な選択であることです。実際には、それは都市とはほとんど関係がなく、広大な土地に適用されます。City の意味が town に変更された場合 (会社が大きな町のデータの収集を開始した場合など)、データベースは完全に壊れてしまいます。
Magician は、正規化の欠如による現在の制限からあなたを救う、正解に近い唯一の答えを持っています。Magician の答えが正規化されていると言うのは正確ではありません。この場合、階層を形成する識別子の選択は正しいです。ただし、「id」列は不要であり、100% 冗長な列、100% 冗長なインデックスであるため、削除します。char() 列はそのままで問題ありませんが、PK (複合キー) についても問題ありません。いずれにせよ、char() 列が一意であることを保証するために、その列にインデックスが必要であることに注意してください。
- これ、つまりリレーショナル識別子を備えたリレーショナル構造があれば、問題は存在しません。
- そして、可哀想なユーザーは愚かなことを考えたり、意味のない識別子を追跡したりする必要がありません。彼らはただこう述べています、 当然:州名、都市名、読み取りタイプ、データ ...。
階層の下端 (City) に到達すると、複合 PK は面倒なもの (3 x CHAR(20) ) になるため、これをデータ テーブルに持ち込みたくありません (特に毎日 CSV インポートがある場合)。都市ごとに多くの読み取り値または行が含まれます)。したがって、City の場合のみ、代理キーを PK として追加します。
しかし、投稿された DDL では、db を正規化したりリレーショナル識別子を使用したりしていない場合でも、はい、City の PK が正しくありません。(idStates, idAreaCode) である必要があり、その逆ではありません。そうすれば問題は解決します。
ところで、ネーミングが非常に悪いです。