9

语境

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

在我们的博客中,我们希望能够设置Labels. 不能单独存在,它们只有在与 a 相关时才会Posts存在。这样,不被任何人使用的不应该留在数据库中。LabelsPostsLabelsPosts

多单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
)

标签

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当我从表中删除对它的所有引用时,不会从数据库中删除LabelPosts。我认为由于 Postgres 给出的原因,尽管 SQLite 在没有警告的情况下接受了该表。

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

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

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

4

2 回答 2

24

我们同时使用 SQLite3(本地/测试)和 PostgreSQL(部署)。

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

其他建议:

  • 正如@Priidu 在评论中提到的那样,您的外键约束是向后的。这没有争议,他们完全错了

  • 在 PostgreSQL 中使用serialorIDENTITY列(Postgres 10+)而不是 SQLite AUTOINCREMENT。看:

  • 使用timestamp(或timestamptz代替datetime

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

  • 不要使用非描述性的列名,例如id. 曾经。这是半智者中间件和 ORM 引入的反模式。当您加入几个表时,您最终会得到 name 的多个列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命令就可以完成这项工作。不需要第二次查询 - 特别是 no 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();

PostgreSQL 中还没有CREATE OR REPLACE TRIGGER只是CREATE TRIGGER

于 2013-03-23T01:46:09.183 回答
3

实现您寻求的行为(从数据库中删除未使用的标签)的一种方法是使用触发器。

您可以尝试编写如下内容:

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 回答