具有外键约束的关系数据库如何摄取可能顺序错误的数据?
How can a relational database with foreign key constraints ingest data that may be in the wrong order?
数据库正在从流中摄取数据,满足外键约束所需的所有行可能迟到或永远不会到达。
这可能通过使用另一个数据存储来实现,一个没有外键约束的数据存储,然后当所有需要的数据可用时,读入具有 fk 约束的数据库。然而,这增加了复杂性,我想避免它。
我们正在研究一种解决方案,该解决方案创建 "placeholder" 行以将外键指向。当真正的数据进来时,占位符被替换为真正的值。同样,这增加了复杂性,但这是我们迄今为止找到的最佳解决方案。
人们通常如何解决这个问题?
编辑:一些示例数据可能有助于解释问题:
假设我们有这些表:
CREATE TABLE order (
id INTEGER NOT NULL,
order_number,
PRIMARY KEY (id),
UNIQUE (order_number)
);
CREATE TABLE line_item (
id INTEGER NOT NULL,
order_number INTEGER REFERENCES order(order_number),
PRIMARY KEY (id)
);
如果我先插入一个订单,没问题!但是假设我尝试:
INSERT INTO line_item (order_number) values (123)
在插入订单 123 之前。这当然会使 fk 约束失败。但这可能是我获取数据的顺序,因为它是从从多个来源收集数据的流中读取的。
此外,为了解决@philpxy 的问题,我在这方面并没有找到太多。提到的一件事是 deferred constraints。这是一种等待在事务结束时执行 fk 约束的机制。但是,我认为在我的情况下不可能这样做,因为只要收到数据,这些插入语句就会随机 运行。
您遇到了业务工作流问题,因为在订单本身进入之前,单个订单的行项目进入。一个可能不理想的解决方法是创建一个插入前触发器,它会检查每个传入插入到 line_item
table,该命令是否已存在于 order
table。如果没有,那么它会先插入订单记录,然后再尝试插入 line_item
.
CREATE OR REPLACE FUNCTION "public"."fn_insert_order" () RETURNS trigger AS $$
BEGIN
INSERT INTO "order" (order_number)
SELECT NEW.order_number
WHERE NOT EXISTS (SELECT 1 FROM "order" WHERE order_number = NEW.order_number);
RETURN NEW;
END
$$
LANGUAGE 'plpgsql'
# trigger
CREATE TRIGGER "trigger_insert_order"
BEFORE INSERT ON line_item FOR EACH ROW
EXECUTE PROCEDURE fn_insert_order()
注意:我假设 order
table 的 id
列实际上是自动递增的,在这种情况下,Postgres 会在插入时自动为其赋值多于。很可能,这就是您想要的,因为有两个都需要手动分配的 id 列没有多大意义。
您可以通过 line_item
上的 BEFORE INSERT
触发器来实现。
在该触发器中,您查询 order
是否存在匹配项,如果不存在,则插入一个虚拟行。
这将允许 INSERT
成功,但会牺牲一些性能。
要将行插入 order
,请使用
INSERT INTO order ...
ON CONFLICT ON (order_number) DO UPDATE SET
id = EXCLUDED.id;
更新主键有问题,可能会导致冲突。解决这个问题的一种方法是,如果您对人工生成的订单使用负数 id
(假设真实的 id
是正数)。如果您对该主键有任何引用,则必须使用 ON UPDATE CASCADE
.
定义约束
数据库正在从流中摄取数据,满足外键约束所需的所有行可能迟到或永远不会到达。
这可能通过使用另一个数据存储来实现,一个没有外键约束的数据存储,然后当所有需要的数据可用时,读入具有 fk 约束的数据库。然而,这增加了复杂性,我想避免它。
我们正在研究一种解决方案,该解决方案创建 "placeholder" 行以将外键指向。当真正的数据进来时,占位符被替换为真正的值。同样,这增加了复杂性,但这是我们迄今为止找到的最佳解决方案。
人们通常如何解决这个问题?
编辑:一些示例数据可能有助于解释问题:
假设我们有这些表:
CREATE TABLE order (
id INTEGER NOT NULL,
order_number,
PRIMARY KEY (id),
UNIQUE (order_number)
);
CREATE TABLE line_item (
id INTEGER NOT NULL,
order_number INTEGER REFERENCES order(order_number),
PRIMARY KEY (id)
);
如果我先插入一个订单,没问题!但是假设我尝试:
INSERT INTO line_item (order_number) values (123)
在插入订单 123 之前。这当然会使 fk 约束失败。但这可能是我获取数据的顺序,因为它是从从多个来源收集数据的流中读取的。
此外,为了解决@philpxy 的问题,我在这方面并没有找到太多。提到的一件事是 deferred constraints。这是一种等待在事务结束时执行 fk 约束的机制。但是,我认为在我的情况下不可能这样做,因为只要收到数据,这些插入语句就会随机 运行。
您遇到了业务工作流问题,因为在订单本身进入之前,单个订单的行项目进入。一个可能不理想的解决方法是创建一个插入前触发器,它会检查每个传入插入到 line_item
table,该命令是否已存在于 order
table。如果没有,那么它会先插入订单记录,然后再尝试插入 line_item
.
CREATE OR REPLACE FUNCTION "public"."fn_insert_order" () RETURNS trigger AS $$
BEGIN
INSERT INTO "order" (order_number)
SELECT NEW.order_number
WHERE NOT EXISTS (SELECT 1 FROM "order" WHERE order_number = NEW.order_number);
RETURN NEW;
END
$$
LANGUAGE 'plpgsql'
# trigger
CREATE TRIGGER "trigger_insert_order"
BEFORE INSERT ON line_item FOR EACH ROW
EXECUTE PROCEDURE fn_insert_order()
注意:我假设 order
table 的 id
列实际上是自动递增的,在这种情况下,Postgres 会在插入时自动为其赋值多于。很可能,这就是您想要的,因为有两个都需要手动分配的 id 列没有多大意义。
您可以通过 line_item
上的 BEFORE INSERT
触发器来实现。
在该触发器中,您查询 order
是否存在匹配项,如果不存在,则插入一个虚拟行。
这将允许 INSERT
成功,但会牺牲一些性能。
要将行插入 order
,请使用
INSERT INTO order ...
ON CONFLICT ON (order_number) DO UPDATE SET
id = EXCLUDED.id;
更新主键有问题,可能会导致冲突。解决这个问题的一种方法是,如果您对人工生成的订单使用负数 id
(假设真实的 id
是正数)。如果您对该主键有任何引用,则必须使用 ON UPDATE CASCADE
.