データベース設計-共有タグ付きのさまざまなオブジェクト


8

私の経歴は、データベース管理ではなく、Webプログラミングに詳しいので、ここで間違った用語を使用している場合は訂正してください。コーディングするアプリケーションのデータベースを設計する最良の方法を見つけようとしています。

状況:レポートが1つの表にあり、推奨事項が別の表にあります。各レポートには多くの推奨事項があります。キーワード用の別のテーブルもあります(タグ付けを実装するため)。ただし、キーワードを検索すると結果としてレポートと推奨事項が表示されるように、レポートと推奨事項の両方に適用されるキーワードのセットを1つだけ用意したいと思います。

これが私が始めた構造です:

Reports
----------
ReportID
ReportName


Recommendations
----------
RecommendationID
RecommendationName
ReportID (foreign key)


Keywords
----------
KeywordID
KeywordName


ObjectKeywords
----------
KeywordID (foreign key)
ReportID (foreign key)
RecommendationID (foreign key)

本能的には、これは最適ではないようで、タグ付け可能なオブジェクトを共通の親から継承し、そのコメントの親にタグを付けると、次のような構造になります。

BaseObjects
----------
ObjectID (primary key)
ObjectType


Reports
----------
ObjectID_Report (foreign key)
ReportName


Recommendations
----------
ObjectID_Recommendation (foreign key)
RecommendationName
ObjectID_Report (foreign key)


Keywords
----------
KeywordID (primary key)
KeywordName


ObjectKeywords
----------
ObjectID (foreign key)
KeywordID (foreign key)

この2番目の構造を使用する必要がありますか?ここに重要な懸念事項がありませんか?また、2番目を使用する場合、「オブジェクト」を置き換えるために、非汎用名として何を使用すればよいですか?

更新:

このプロジェクトではSQL Serverを使用しています。これは、非同時ユーザーが少数の内部アプリケーションであるため、高負荷は予想されません。使用に関しては、キーワードは控えめに使用される可能性があります。これは、統計レポートの目的のためだけのものです。その意味で、私が行った解決策はおそらく、このシステムを将来にわたって維持する必要のある開発者にのみ影響を及ぼします...しかし、可能な場合はいつでも適切な方法を実装することが良いと考えました。すべての洞察をありがとう!


最も重要な質問への回答がありません-データはどのようにアクセスされますか?-どのクエリ/ステートメントに対してモデルを「調整」しますか?-どのように機能を拡張する予定ですか?一般的なベストプラクティスはないと思います。解決策はこれらの質問の答えに依存します。そして、このような単純なモデルでも重要になります。または、いくつかのより高い原則に従うモデルになってしまう可能性がありますが、実際には最も重要なシナリオ(システムのユーザーから見たシナリオ)には影響があります。
ステファンOravec

いい視点ね!私はこれについて少し時間を費やす必要があります!
matikin9 2013年

回答:


6

最初の例の問題は、トライリンクテーブルです。それは、レポートまたは推奨事項のいずれかの外部キーの1つが常にNULLであることを要求し、キーワードがどちらか一方のみにリンクするようにしますか?

2番目の例の場合、ベースから派生テーブルへの結合では、方法に応じてタイプセレクタまたはLEFT JOINを使用する必要がある場合があります。

それでは、なぜそれを明示的にして、すべてのNULLとLEFT JOINを削除しないのですか?

Reports
----------
ReportID
ReportName


Recommendations
----------
RecommendationID
RecommendationName
ReportID (foreign key)


Keywords
----------
KeywordID
KeywordName


ReportKeywords
----------
KeywordID (foreign key)
ReportID (foreign key)

RecommendationKeywords
----------
KeywordID (foreign key)
RecommendationID (foreign key)

このシナリオでは、タグ付けする必要がある何かを追加する場合、エンティティテーブルとリンケージテーブルを追加するだけです。

次に、検索結果は次のようになります(単一の結果リストが必要な場合は、まだタイプ選択が行われていて、オブジェクト結果レベルでジェネリックに変換されます)。

SELECT CAST('REPORT' AS VARCHAR(15)) AS ResultType
    ,Reports.ReportID AS ObjectID
    ,Reports.ReportName AS ObjectName
FROM Keywords
INNER JOIN ReportKeywords
    ON ReportKeywords.KeywordID = Keywords.KeywordID
INNER JOIN Reports
    ON Reports.ReportID = ReportKeywords.ReportID
WHERE Keywords.KeywordName LIKE '%' + @SearchCriteria + '%'
UNION ALL
SELECT 'RECOMMENDATION' AS ResultType
    ,Recommendations.RecommendationID AS ObjectID
    ,Recommendations.RecommendationName AS ObjectName
FROM Keywords
INNER JOIN RecommendationKeywords
    ON RecommendationKeywords.KeywordID = Keywords.KeywordID
INNER JOIN Recommendations
    ON Recommendations.RecommendationID = RecommendationKeywords.ReportID
WHERE Keywords.KeywordName LIKE '%' + @SearchCriteria + '%'

とにかく、どこかで型の選択が行われ、ある種の分岐が行われます。

オプション1でこれを行う方法を見ると、これは似ていますが、CASEステートメントまたはLEFT JOINとCOALESCEのいずれかを使用しています。リンクされるものを増やしてオプション2を拡張するとき、通常は見つからない場所にLEFT JOINを追加し続ける必要があります(リンクされるオブジェクトは、有効な派生テーブルを1つしか持つことができません)。

オプション2に根本的な問題はないと思います。実際に、ビューを使用してこの提案のように見せることもできます。

オプション1では、なぜトライリンクテーブルを選択したのかわかりません。


あなたが言及しているトライリンクテーブルは、おそらく私が精神的に怠惰だった結果でした...:Pさまざまな答えを読んだ後、私の最初のオプションはどちらも意味がないと思います。別々のReportKeywordsテーブルとRecommendationKeywordsテーブルを用意することは、より実用的な意味があります。キーワードの適用が必要なオブジェクトが増える可能性があるという点でスケーラビリティを検討していましたが、現実的には、キーワードが必要なオブジェクトタイプはおそらく1つしかありません。
matikin9 2013年

4

まず、理想的なソリューションは、使用するRDBMSにある程度依存することに注意してください。次に、標準的な回答とPostgreSQL固有の回答の両方を示します。

正規化された標準回答

標準的な答えは、2つの結合テーブルを持つことです。

テーブルがあるとします。

CREATE TABLE keywords (
     kword text
);

CREATE TABLE reports (
     id serial not null unique,
     ...
);

CREATE TABLE recommendations (
     id serial not null unique,
     ...
);

CREATE TABLE report_keywords (
     report_id int not null references reports(id),
     keyword text not null references keyword(kword),
     primary key (report_id, keyword)
);

CREATE TABLE recommendation_keywords (
     recommendation_id int not null references recommendation(id),
     keyword text not null references keyword(kword),
     primary key (recommendation_id, keyword)
);

このアプローチは、すべての標準正規化ルールに従い、従来のデータベース正規化の原則に違反しません。どのRDBMSでも機能するはずです。

PostgreSQL固有の回答、N1NF設計

最初に、なぜPostgreSQLが異なるのかについて一言。PostgreSQLは、配列に対してインデックスを使用する非常に便利な方法をいくつかサポートしています。特に、GINインデックスと呼ばれるものを使用しています。これらをここで適切に使用すると、パフォーマンスが大幅に向上します。PostgreSQLはこの方法でデータ型に「到達」できるため、原子性と正規化の基本的な仮定は、ここに厳密に適用するにはやや問題があります。したがって、この理由から、私の推奨は、最初の正規形の原子性規則を破り、パフォーマンスを向上させるためにGINインデックスに依存することです。

ここで2番目の注意点は、これによりパフォーマンスは向上しますが、参照整合性を正しく機能させるために手動で行う必要があるため、いくつかの問題が追加されます。したがって、ここでのトレードオフは、手動作業のパフォーマンスです。

CREATE TABLE keyword (
    kword text primary key
);

CREATE FUNCTION check_keywords(in_kwords text[]) RETURNS BOOL LANGUAGE SQL AS $$

WITH kwords AS ( SELECT array_agg(kword) as kwords FROM keyword),
     empty AS (SELECT count(*) = 0 AS test FROM unnest($1)) 
SELECT bool_and(val = ANY(kwords.kwords))
  FROM unnest($1) val
 UNION
SELECT test FROM empty WHERE test;
$$;

CREATE TABLE reports (
     id serial not null unique,
     ...
     keywords text[]   
);

CREATE TABLE recommendations (
     id serial not null unique,
     ...
     keywords text[]  
);

次に、キーワードを適切に管理するためにトリガーを追加する必要があります。

CREATE OR REPLACE FUNCTION trigger_keyword_check() RETURNS TRIGGER
LANGUAGE PLPGSQL AS
$$
BEGIN
    IF check_keywords(new.keywords) THEN RETURN NEW
    ELSE RAISE EXCEPTION 'unknown keyword entered'
    END IF;
END;
$$;

CREATE CONSTRAINT TRIGGER check_keywords AFTER INSERT OR UPDATE TO reports
WHEN (old.keywords <> new.keywords)
FOR EACH ROW EXECUTE PROCEDURE trigger_keyword_check();

CREATE CONSTRAINT TRIGGER check_keywords AFTER INSERT OR UPDATE 
TO recommendations
WHEN (old.keywords <> new.keywords)
FOR EACH ROW EXECUTE PROCEDURE trigger_keyword_check();

次に、キーワードが削除されたときに何をするかを決定する必要があります。現在のところ、キーワードテーブルから削除されたキーワードは、キーワードフィールドにカスケードされません。多分これは望ましいことかもしれませんし、そうでないかもしれません。最も簡単なことは、削除を常に制限することであり、削除が発生した場合は手動でこのケースを処理することを期待します(ここでは安全のためにトリガーを使用します)。別のオプションは、キーワードが存在するすべてのキーワード値を書き換えて、それを削除することです。繰り返しになりますが、トリガーもその方法です。

このソリューションの大きな利点は、キーワードによる非常に高速な検索のためにインデックスを作成できることと、結合なしですべてのタグをプルできることです。欠点は、キーワードを削除するのは面倒であり、良い日でもうまく機能しないことです。これはまれなイベントであり、バックグラウンドプロセスに委託される可能性がありますが、理解する価値のあるトレードオフであるため、これは許容できる場合があります。

最初のソリューションを批評する

最初のソリューションの本当の問題は、ObjectKeywordsにキーがない可能性があることです。その結果、各キーワードが各オブジェクトに一度だけ適用されることを保証できないという問題があります。

2番目のソリューションは少し優れています。提供されている他のソリューションが気に入らない場合は、それを使用することをお勧めします。ただし、keyword_idを削除して、キーワードテキストに参加することをお勧めします。これにより、非正規化せずに結合が排除されます。


このプロジェクトではMS SQL Serverを使用していますが、PostgreSQLの情報に感謝します。削除して、オブジェクトとキーワードのペアがそれぞれ1回だけ発生することを確認することについて述べた他のポイント。オブジェクトとキーワードのペアごとにキーがあったとしても、挿入する前に確認する必要はありませんか?個別のキーワードIDについては... SQL Serverでそれを読みましたが、文字列が長すぎるとパフォーマンスが低下する可能性があり、おそらくユーザーが「キーワード」ではなく「キーフレーズ」を入力できるようにする必要があります。 「。
matikin9 2013年

0

私は2つの別々の構造を提案します:

report_keywords
---------------
  レポートID
  キーワードID

recommendation_keywords
-----------------------
  recommendation_id
  keyword_id

この方法では、同じテーブルにすべての可能なエンティティIDがなく(非常にスケーラブルではなく、混乱を招く可能性があります)、他の場所を明確にする必要がある一般的な「オブジェクトID」を持つテーブルがありません。base_objectテーブルを使用すると機能しますが、デザインが複雑すぎると思います。


あなたの提案が実行可能なオプションであることに同意しませんが、なぜOPのデザインBでRIを強制できないのですか?(私はそれがあなたの言っていることだと思います)。
ypercubeᵀᴹ

@ypercube:BaseObjects最初のリードスルーでテーブルを逃したと思います。またobject_id任意のテーブルのIDを指すことができるテーブルの説明が表示されていると思いました。
FrustratedWithFormsDesigner 2013年

-1

私の経験では、これはあなたができることです。

Reports
----------
Report_id (primary_key)
Report_name

Recommendations
----------------
Recommendation_id (primary key)
Recommendation_name
Report_id (foreign key)

Keywords
----------
Keyword_id (primary key)
Keyword

キーワード、レポート、推奨事項の関係については、次の2つのオプションのいずれかを実行できます。オプションA:

Recommendation_keywords
------------------------
Recommendation_id(foreign_key)
keyword_id (foreign_key)

これにより、レポートからレコメンデーション、キーワード、そして最終的にキーワードへの直接的な関係が可能になります。オプションB:

object_keywords
---------------
Object_id
Object_type
Keyword_id(foreign_key)

オプションAは、データの整合性を処理するデータベースの制約があり、無効なデータの挿入を許可しないため、適用と管理が簡単です。

オプションBでは、関係の識別をコーディングする必要があるため、もう少し作業が必要です。長期的にはより柔軟です。将来のある時点で、レポートまたは推奨事項以外の別のアイテムにキーワードを追加する必要がある場合は、IDを追加してテーブルを直接使用するだけで済みます。


私が反対票を投じた理由を説明しましょう。1.オプションA、B、または3番目のアプローチを支持しているかどうかは明確ではありません。(私には)どちらもだいたい大丈夫だと思われます(Aには他の人が回答で概説しているいくつかの問題があるため、同意しません。2。A(またはB)の設計を改善することを提案していますか? ?どちらも明確ではありません。FKを明確に定義しておくことも良いでしょう。提案する内容がまったく明確ではありません。全体として、今後の訪問者のために物事やオプションを明確にする回答が好きです。回答を編集して、私は私の票を逆にします。
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.