【问题标题】:How can I design these tables to prevent corrupted data where one table's foreign keys' referenced rows must agree?如何设计这些表以防止一个表的外键引用行必须一致的损坏数据?
【发布时间】:2017-12-03 20:49:39
【问题描述】:

我有 4 张桌子:

users
id integer primary_key

questions
id integer primary_key
asker_id references users(id)

answers
id integer primary_key
question_id references questions(id)
answerer_id references users(id)

notifications
id integer primary_key
answer_id references answers(id)
notified_user_id references users(id)

如何强制执行以下约束:

任何通知中的notified_user_id 必须与该通知中answer_id 引用的问题中的asker_id 相同。

基本上,我想确保我通知提出问题的用户,而不是任何其他用户。

我知道我可以正常化,但我需要 notified_user_id 存在于通知中。

【问题讨论】:

  • 既然你坚持让它非规范化,只需在你的应用逻辑中强制执行约束。你不能同时拥有它。
  • "我知道我可以正常化,但我需要 notify_user_id 存在于通知中" 。为什么?为什么不能在运行时使用 select 语句来连接表?您可以对此类事情使用触发器。但这通常表明存在设计问题
  • @Nick.McDermaid 因为我认为加入 4 张桌子很昂贵。
  • 比一个比它需要的更复杂的系统的支持开销更昂贵?
  • 衡量费用。这将取决于音量。有几百个问题,费用将是微不足道的。面对数十亿个问题,您需要一个非常好的平台。

标签: sql postgresql database-design foreign-keys constraints


【解决方案1】:

首先,您需要知道在此架构中,答案和通知之间存在 1:N 关系(因此对于每个答案,您将能够拥有多个通知)。

如果不是您想要的,只需在answers 表中添加一列answerer_is_notified 标志(例如boolean 数据类型)并删除notifications 表。

其次,如果您确实想采用这种非规范化方法(存在冗余的函数依赖关系,让您思考如何解决缺乏约束的问题,仅使用 FK 建模是不可能的),让我们创建一个触发器:

create or replace function notifications_check_user_id_trigger()
  returns trigger as $$
begin
  if new.notified_user_id <> 
    (select answerer_id from answers where id = new.answer_id)
  then
    raise exception e'answer_id (%) and notified_user_id (%) don\'t match',
      new.answer_id,
      new.notified_user_id;
  end if;
end;
$$ language plpgsql;

create trigger t_50_notifications_check_user_id_trigger
  before insert or update
  on notifications
  for each row execute procedure notifications_check_user_id_trigger()
;

附言实际上并没有检查此代码,可能存在一些拼写错误。下次,请提供样板 DDL SQL 以简化回答。

【讨论】:

  • @philipxy 这里有一个明显的 FD:notifications.notified_user_id 在功能上依赖于notifications. answer_id (notifications.answer_idanswers.question_idquestions.asker_id)。标准化比您想象的要多,请阅读它(至少大约 1NF、2NF、3NF)en.wikipedia.org/wiki/Database_normalization
  • 1. “存在冗余的功能依赖关系,使您认为如何解决缺乏约束的问题,仅使用 FK 无法建模”是难以理解的。 2. FD 保存在表格中,因此您的注释在表格之间误用了“→”。您可能正在考虑一张分解为这些表格的表格;你不清楚。 3.尚不清楚您所指的“这种非规范化方法”是指什么,但问题中的每个表都在 5NF 中,因此没有一个表是“非规范化的”。 4. 我建议你阅读自己的链接,但维基百科充满了废话重新规范化,所以请参阅学术教科书。
  • 不,我没有滥用任何东西。想象一下,您采用该结构并用数据填充它。作者明确说明了notified_user_id是什么意思。打开您喜欢的“学术书籍”(由 Date、Ullman、Darwen 或其他 smb 提供)并阅读 FD 是如何定义的。然后尝试将其应用于数据。当然在单桌之内。你会看到有FD。如果您仍然看不到,我无能为力,对不起:)
  • 您刚刚证实了我的猜想,您并不清楚地试图引用您“正在考虑”/“想象”。我的其他观点也是正确的。祝你好运。
  • 不,你的观点完全错误——这里的数据是非规范化的,一列依赖另一列,这只是理解FD和NF*是什么的错误。
【解决方案2】:

任何通知中的 notify_user_id 必须与该通知中 answer_id 引用的问题中的 asker_id 相同

为什么还要通知notified_user_id?考虑到其他表,这是多余的。通知的用户是问题的提问者answer_id。加入即可获得他们的 id。

如果您确实想要notified_user_id,则以声明方式将question_id 添加到notifications 进行约束。

-- notification *id* is to asker *notified_user_id* re answer *answer_id* to their question *question_id*
notifications
    id integer primary_key
    answer_id
    notified_user_id
    question_id
    (answer_id, question_id) references answers(id, question_id)
    (question_id, notified_user_id) references questions(id, asker_id)

现在notified_user_idquestion_id 都是多余的,但它们受到了适当的约束。

PS 从通知中删除notified_user_id 会减少冗余,但这不是规范化解决的那种冗余,删除也不是规范化。此外,您(已评论)在放弃“昂贵”时加入的概念是错误的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多