多对多关系中的外键关键

Ant*_*neG 9 sql sqlite postgresql database-design many-to-many

上下文

我们正在建立一个介绍博客.到数据库课程项目.

在我们的博客中,我们希望能够Labels开启Posts.它Labels本身不存在,只有与a相关时才会存在Posts.这样,Labels任何不使用的都不Posts应该留在数据库中.

不止一个Label可以属于单个Post,而且多个Post可以使用一个Label.

我们正在使用SQLite3(本地/测试)和PostgreSQL(部署).

履行

下面是我们用来创建这两个表的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
)
Run Code Online (Sandbox Code Playgroud)

标签

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 
)
Run Code Online (Sandbox Code Playgroud)

LabelPosts(Post[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
)
Run Code Online (Sandbox Code Playgroud)

问题

  • Labels当我从LabelPosts表中删除对它的所有引用时,不会从数据库中删除使用SQLite3 .我认为由于Postgres给出的理由,尽管SQLite在没有警告的情况下接受了这个表.

  • PostgreSQL抱怨labelId内部不是唯一的LabelPosts,这是真的,也是必需的,因为它是多对多的:

pq:S:"ERROR"R:"transformFkeyCheckAttrs"L:"6511"C:"42830"F:"tablecmds.c"
M:"没有唯一约束匹配给定引用表的键""labelposts \""

所以我明白我的约束是错误的.但是我不知道如何正确地做到这一点.

Erw*_*ter 22

  • 你的第一个大错:

我们正在使用SQLite3(本地/测试)和PostgreSQL(部署).

这是在乞求麻烦.您将继续遇到轻微的不兼容性.或者甚至在很久以后,当损坏完成时才注意到它们.不要这样做.也可以在本地使用PostgreSQL.它可以免费用于大多数操作系统.对于参与"数据库课程项目"的人来说,这是一个令人惊讶的愚蠢行为.

  • 在PostgreSQL中使用serial而不是SQLite AUTOINCREMENT.
    使用timestamp(或timestamptz)代替datetime.

  • 不要使用大小写混合标识符.

  • 不要使用非描述性列名id.永远.这是由半智能中间件和ORM引入的反模式.当您加入几个表时,您最终会得到多个名称列id.那是积极的伤害.

  • 有许多命名样式,但大多数人认为最好将单数术语作为表名.它更短,至少是直观/合乎逻辑的.label不是labels.

  • 正如@Priidu在评论中提到的,您的外键约束是倒退的.这不是辩论,它们完全错了.

一切都放在一起,它可能看起来像这样:

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)
);
Run Code Online (Sandbox Code Playgroud)

触发

CREATE OR REPLACE FUNCTION f_trg_kill_orphaned_label() 
  RETURNS TRIGGER AS
$func$
BEGIN
   DELETE FROM label
   WHERE  label_id = OLD.label_id
   AND    NOT EXISTS (
      SELECT 1 FROM label_post
      WHERE  label_id = OLD.label_id
      );
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
  • 必须在触发创建触发器功能.

  • 一个简单的DELETE命令可以完成这项工作.不需要第二次查询 - 特别是没有count(*).EXISTS更便宜.

  • 周围没有单引号plpgsql.这是一个标识符,而不是一个值!

CREATE TRIGGER label_post_delaft_kill_orphaned_label
AFTER DELETE ON label_post
FOR EACH ROW EXECUTE PROCEDURE f_trg_kill_orphaned_label();
Run Code Online (Sandbox Code Playgroud)

有没有CREATE OR REPLACE TRIGGER在PostgreSQL的,但.只是CREATE TRIGGER.

  • @MohamedMansour:嗯,我不同意.ORM是原始的拐杖,经常无法充分发挥RDBMS的潜力.使用Oracle,降低成本可能是有意义的,但这不适用于PostgreSQL.即使您使用ORM,使用不同的RDBMS也无济于事.你只能输.您需要快速查询并决定使用某个查询,因为它可以提供最佳性能?你猜怎么着?在高效的数据库上并不是真的......如果可以的话,使用相同的RDBMS进行开发!这是一个明智的选择. (8认同)