如何处理其中一个可为空的相关复合外键?
How to deal with related composite foreign keys with one of them being nullable?
以下是相关架构部分:
CREATE TABLE "Alarm" (
"id" SERIAL PRIMARY KEY NOT NULL
);
CREATE TABLE "AlarmSensor" (
"alarmId" INTEGER NOT NULL REFERENCES "Alarm"("id"),
"sensorId" INTEGER NOT NULL REFERENCES "Sensor"("id"),
PRIMARY KEY ("alarmId", "sensorId")
);
CREATE TABLE "AlarmUser" (
"alarmId" INTEGER NOT NULL REFERENCES "Alarm"("id"),
"userId" INTEGER NOT NULL REFERENCES "User"("id"),
PRIMARY KEY ("alarmId", "userId")
);
CREATE TABLE "AlarmOccurrence" (
"id" SERIAL PRIMARY KEY NOT NULL,
"time" TIMESTAMP NOT NULL DEFAULT now(),
"alarmId" INTEGER NOT NULL,
"sensorId" INTEGER NOT NULL,
"acknowledgedAt" TIMESTAMP NULL,
"acknowledgedByUserId" INTEGER NULL,
-- Both FKs share the same alarmId, but I want acknowledgedByUserId to be initially null
FOREIGN KEY ("alarmId", "sensorId") REFERENCES "AlarmSensor"("alarmId", "sensorId"),
FOREIGN KEY ("alarmId", "acknowledgedByUserId") REFERENCES "AlarmUser"("alarmId", "userId")
);
你看,我不是数据库设计专家。所以如果你认为我可以以某种方式改进它,我很乐意学习如何去做!无论如何,我可以创建一个新的 AlarmOccurrence 并将 acknowledgeByUserId 初始设置为 null 吗?目前,要创建一个新的 AlarmOccurrence,我必须 connect/create 一个用户来填写 alarmId/userId 外键。我猜那是因为 alarmId 不可为空。
编辑:我发现这是一个 ORM 问题。我可以使用原始 SQL 根据需要插入到 table。但是在应用程序代码中,两个 FK 都被标记为必需。我将打开一个单独的问题。非常感谢您的帮助。
您有两个选择:
使用默认选项定义外键MATCH SIMPLE
然后,如果定义外键的任何列为 NULL,则不强制执行外键。
用MATCH FULL
定义外键
然后所有外键列必须为 NOT NULL 或全部为 NULL(在这种情况下不强制执行外键)。
阅读 CREATE TABLE
的文档了解更多信息。
有抱负的数据库设计者请注意:永远不要使用双引号标识符。否则,您将不得不在所有 SQL 语句中使用双引号,这会让您的生活比必要的更艰难。
SQL 标准定义了部分可为空的外键的“匹配类型”。它具有三个可能的值。
匹配完整:整个外键必须为空或不为空。如果不为空,则针对引用的 table.
强制执行
匹配 部分:如果 FK 的某些部分为空,则在引用的 table 中验证 non-null 列;在引用的 table 中必须至少有一行具有这些值。尽管在文档中提到了此模式,但 PostgreSQL 并未实现此模式。
匹配简单:如果 FK 中有空值,它会被接受但不会根据引用的 table.
进行验证
在您的情况下,“FULL”似乎对您来说是正确的选择。您可以在 PostgreSQL ALTER TABLE.
查看语法
以下是相关架构部分:
CREATE TABLE "Alarm" (
"id" SERIAL PRIMARY KEY NOT NULL
);
CREATE TABLE "AlarmSensor" (
"alarmId" INTEGER NOT NULL REFERENCES "Alarm"("id"),
"sensorId" INTEGER NOT NULL REFERENCES "Sensor"("id"),
PRIMARY KEY ("alarmId", "sensorId")
);
CREATE TABLE "AlarmUser" (
"alarmId" INTEGER NOT NULL REFERENCES "Alarm"("id"),
"userId" INTEGER NOT NULL REFERENCES "User"("id"),
PRIMARY KEY ("alarmId", "userId")
);
CREATE TABLE "AlarmOccurrence" (
"id" SERIAL PRIMARY KEY NOT NULL,
"time" TIMESTAMP NOT NULL DEFAULT now(),
"alarmId" INTEGER NOT NULL,
"sensorId" INTEGER NOT NULL,
"acknowledgedAt" TIMESTAMP NULL,
"acknowledgedByUserId" INTEGER NULL,
-- Both FKs share the same alarmId, but I want acknowledgedByUserId to be initially null
FOREIGN KEY ("alarmId", "sensorId") REFERENCES "AlarmSensor"("alarmId", "sensorId"),
FOREIGN KEY ("alarmId", "acknowledgedByUserId") REFERENCES "AlarmUser"("alarmId", "userId")
);
你看,我不是数据库设计专家。所以如果你认为我可以以某种方式改进它,我很乐意学习如何去做!无论如何,我可以创建一个新的 AlarmOccurrence 并将 acknowledgeByUserId 初始设置为 null 吗?目前,要创建一个新的 AlarmOccurrence,我必须 connect/create 一个用户来填写 alarmId/userId 外键。我猜那是因为 alarmId 不可为空。
编辑:我发现这是一个 ORM 问题。我可以使用原始 SQL 根据需要插入到 table。但是在应用程序代码中,两个 FK 都被标记为必需。我将打开一个单独的问题。非常感谢您的帮助。
您有两个选择:
使用默认选项定义外键
MATCH SIMPLE
然后,如果定义外键的任何列为 NULL,则不强制执行外键。
用
定义外键MATCH FULL
然后所有外键列必须为 NOT NULL 或全部为 NULL(在这种情况下不强制执行外键)。
阅读 CREATE TABLE
的文档了解更多信息。
有抱负的数据库设计者请注意:永远不要使用双引号标识符。否则,您将不得不在所有 SQL 语句中使用双引号,这会让您的生活比必要的更艰难。
SQL 标准定义了部分可为空的外键的“匹配类型”。它具有三个可能的值。
匹配完整:整个外键必须为空或不为空。如果不为空,则针对引用的 table.
强制执行匹配 部分:如果 FK 的某些部分为空,则在引用的 table 中验证 non-null 列;在引用的 table 中必须至少有一行具有这些值。尽管在文档中提到了此模式,但 PostgreSQL 并未实现此模式。
匹配简单:如果 FK 中有空值,它会被接受但不会根据引用的 table.
进行验证
在您的情况下,“FULL”似乎对您来说是正确的选择。您可以在 PostgreSQL ALTER TABLE.
查看语法