9

コンテクスト

イントロ用のブログを作成しています。データベースコースプロジェクトへ。

私たちのブログでは、に設定できるようにしたいと考えてLabelsいますPosts。はLabels単独で存在することはできません。存在するのは、に関連している場合のみですPosts。このように、Labels誰も使用していないものをPostsデータベースに残してはなりません。

複数が1つLabelに属することができPost、複数Postがを使用できますLabel

SQLite3(ローカル/テスト)とPostgreSQL(デプロイメント)の両方を使用しています。

実装

これらの2つのテーブルをリレーションシップテーブルとともに作成するために使用するSQL(SQLite3フレーバー)は次のとおりです。

投稿

CREATE TABLE IF NOT EXISTS Posts(
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   authorId INTEGER,
   title VARCHAR(255),
   content TEXT,
   imageURL VARCHAR(255),
   date DATETIME,
   FOREIGN KEY (authorId) REFERENCES Authors(id) ON DELETE SET NULL
)

ラベル

CREATE TABLE IF NOT EXISTS Labels(
   id INTEGER PRIMARY KEY AUTOINCREMENT,
   name VARCHAR(255) UNIQUE,
   -- This is not working:
   FOREIGN KEY (id) REFERENCES LabelPosts(labelId) ON DELETE CASCADE 
)

LabelPostsPost ( [1 .. *]-*間の関係Label

CREATE TABLE IF NOT EXISTS LabelPosts(
    postId INTEGER,
    labelId INTEGER,
    PRIMARY KEY (postId, labelId),
    FOREIGN KEY (postId) REFERENCES Posts(id) ON DELETE CASCADE
)

問題

  • SQLite3を使用して、テーブルLabelsからSQLite3へのすべての参照を削除しても、データベースから削除されません。LabelPostsSQLiteが警告なしにテーブルを受け入れたにもかかわらず、Postgresによって与えられた理由のためだと思います。

  • PostgreSQLは、labelId内で一意ではないと文句を言いますLabelPosts。これは、多対多であるため、真実であり、必須でもあります。

pq:S: "ERROR" R: "transformFkeyCheckAttrs" L: "6511" C: "42830" F: "tablecmds.c"
M:"参照テーブル\"labelposts\"の特定のキーに一致する一意の制約はありません"

だから私は自分の制約を間違っていることを理解しています。しかし、私はそれを正しく行う方法がわかりません。

4

2 に答える 2

24

SQLite3(ローカル/テスト)とPostgreSQL(デプロイメント)の両方を使用しています。

これはトラブルを懇願しています。あなたはマイナーな非互換性に遭遇し続けるでしょう。または、ダメージが発生するずっと後まで、それらに気付かないでください。しないでください。ローカルでもPostgreSQLを使用します。ほとんどすべてのOSで無料で利用できます。「データベースコースプロジェクト」に携わっている人にとって、これは驚くべき愚かさです。関連している:

その他のアドバイス:

  • @Priiduがコメントで述べたように、外部キーの制約は後方にあります。これは議論の余地がなく、単に間違っています。

  • PostgreSQLでは、SQLiteの代わりにserialまたはIDENTITY列(Postgres 10以降)を使用しAUTOINCREMENTます。見る:

  • の代わりにtimestamp(またはtimestamptzを使用しdatetimeます。

  • 大文字と小文字が混在する識別子は使用しないでください。

  • のようなわかりにくい列名は使用しないでくださいid。これまで。これは、半ばウィットのミドルウェアとORMによって導入されたアンチパターンです。いくつかのテーブルを結合すると、名前の複数の列ができあがりますid。それは積極的に傷つきます。

  • 多くの命名スタイルがありますが、ほとんどの場合、テーブル名として単一の用語を使用する方がよいことに同意しています。それはより短く、少なくとも直感的/論理的です。label、ではありませんlabels

すべてをまとめると、次のようになります。

CREATE TABLE IF NOT EXISTS post (
   post_id   serial PRIMARY KEY
 , author_id integer
 , title     text
 , content   text
 , image_url text
 , date      timestamp
);

CREATE TABLE IF NOT EXISTS label (
   label_id  serial PRIMARY KEY
 , name      text UNIQUE
);

CREATE TABLE IF NOT EXISTS label_post(
    post_id  integer REFERENCES post(post_id) ON UPDATE CASCADE ON DELETE CASCADE
  , label_id integer REFERENCES label(label_id) ON UPDATE CASCADE ON DELETE CASCADE
  , PRIMARY KEY (post_id, label_id)
);

引き金

未使用のラベルを削除するには、トリガーを実装します。@Priiduが提供するバージョンに満足していないので、別のバージョンを提供します。

CREATE OR REPLACE FUNCTION f_trg_kill_orphaned_label() 
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
BEGIN
   DELETE FROM label l
   WHERE  l.label_id = OLD.label_id
   AND    NOT EXISTS (
      SELECT 1 FROM label_post lp
      WHERE  lp.label_id = OLD.label_id
      );
END
$func$;
  • トリガー関数は、トリガーのに作成する必要があります。

  • 簡単なDELETEコマンドでその仕事をすることができます。2番目のクエリは必要ありません-特にありませんcount(*)EXISTS安いです。

  • 言語名を一重引用符で囲むことは許容されますが、実際には識別子であるため、ナンセンスを省略してください。LANGUAGE plpgsql

CREATE TRIGGER label_post_delaft_kill_orphaned_label
AFTER DELETE ON label_post
FOR EACH ROW EXECUTE PROCEDURE f_trg_kill_orphaned_label();

CREATE OR REPLACE TRIGGERPostgreSQLにはまだありません。ただCREATE TRIGGER

于 2013-03-23T01:46:09.183 に答える
3

求める動作(データベースから未使用のラベルを削除する)を実現する1つの方法は、トリガーを使用することです。

次のようなものを書いてみてください。

CREATE OR REPLACE TRIGGER tr_LabelPosts_chk_no_more_associated_posts 
AFTER DELETE ON LabelPosts 
FOR EACH ROW 
EXECUTE PROCEDURE f_LabelPosts_chk_no_more_associated_posts();


CREATE OR REPLACE FUNCTION f_LabelPosts_chk_no_more_associated_posts() 
RETURNS TRIGGER AS $$
DECLARE
    var_associated_post_count INTEGER;
BEGIN
    SELECT Count(*) AS associated_post_count INTO var_associated_post_count FROM LabelPosts WHERE labelId = OLD.labelId;
    IF(var_associated_post_count = 0) THEN
        DELETE FROM Labels WHERE labelId = OLD.labelId;
    END IF;
END
$$ LANGUAGE 'plpgsql';

基本的に、ここで何が起こるかは次のとおりです。

  1. テーブルから行が削除されますPosts
  2. 削除は、の関連するすべての行にカスケードされLabelPostsます(外部キー制約のおかげで)。
  3. トリガーのすべての行の削除LabelPostsがアクティブ化された後、PostgreSQL関数が呼び出されます。
  4. この関数は、問題に関連する他の投稿があるかどうかをチェックlabelIdします。もしそうなら、それはそれ以上の変更なしで終了します。ただし、リレーションシップテーブルに他の行がない場合、ラベルは他の場所では使用されないため、削除できます。
  5. この関数は、テーブルに対してDMLの削除を実行しLabels、(現在の)未使用のラベルを効果的に削除します。

明らかに、名前付けは最適ではなく、そこには大量の構文エラーがあるはずです。詳細については、ここここを参照してください。これを取り除くためのより良い方法があるかもしれませんが、現時点では、見栄えの良い一般的なテーブル構造を破壊しない高速な方法は考えられません。

念頭に置いておく必要はありませんが、データベースにトリガーを過剰に使用することは一般的には適切な方法ではありません。これにより、関連するすべてのクエリ/ステートメントの実行が遅くなり、管理がかなり困難になります。(トリガーの性質によっては、特定のDML操作などを実行するためにトリガーを無効にする必要がある場合があります)。

于 2013-03-23T01:32:39.433 に答える