Sno*_*all 5 postgresql database-design referential-integrity spatial exclusion-constraint
我正在尝试对电路板上零件的放置进行建模。没有任何有意义的限制,我的基本架构如下所示:
create table part (
part_id bigserial primary key,
name text not null,
width double precision not null,
height double precision not null
);
create table board (
board_id bigserial primary key,
width double precision not null,
height double precision not null
);
create table board_part (
board_id bigint not null references board,
part_id bigint not null references part,
position point not null
);
Run Code Online (Sandbox Code Playgroud)
对于b
和b2
any board_part
s,我想强制执行以下约束:
b
位于黑板上:
box(b.position, point(b.part.width,b.part.height))
<@ box(point(0,0), point(b.board.width,b.board.height))
Run Code Online (Sandbox Code Playgroud)b
b2
如果它们位于同一块板上,则不会重叠:
b.board_id != b2.board_id or
not (box(b.position, point(b.part.width,b.part.height))
&& box(b2.position, point(b2.part.width,b2.part.height)))
Run Code Online (Sandbox Code Playgroud)我怎样才能实现这一点(没有太多的数据重复)?改变架构就好了。
这是我的最佳尝试(SQL Fiddle),从埃尔文对我之前问题的回答中汲取灵感。它强制执行我想要的约束,但表中有很多重复数据board_part
。我想我可以编写一个函数来自动填充board_width
、board_height
、part_width
和part_height
字段,但周围有这么多重复数据仍然感觉不对。另外,键入width
/height
字段感觉就像黑客。
我建议使用几何类型box
和排除约束(Postgres 9.2+)。应该是完美的解决您的问题。它隐式创建一个也支持某些查询的 GiST 索引。
将其与 equal on 结合起来board_id
以允许在一个表中包含多个板。您将需要附加模块btree_gist
。每个数据库一次:
CREATE EXTENSION btree_gist;\n
Run Code Online (Sandbox Code Playgroud)\n要将零件限制在板上,请添加CHECK
约束。您需要板子的限制盒board_part
(冗余)。可能看起来像这样:
CREATE TABLE board_part (\n board_id bigint NOT NULL REFERENCES board\n, board_box box NOT NULL\n, part_id bigint NOT NULL REFERENCES part\n, part_box box NOT NULL\n, EXCLUDE USING gist (board_id WITH =, part_box WITH &&)\n, CHECK (part_box <@ board_box)\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n缺点:您需要表中每个零件和盒子的尺寸board_part
多余的每个零件和盒子的尺寸。
为了避免冗余存储,我更喜欢这个新想法:FakeIMMUTABLE
函数返回 id 的框并在这些函数上构建约束。您的表格只需使用原始设计的列即可。
我进行了完整的测试来验证它是否有效。
\n基表:
\nCREATE TABLE part (\n part_id serial PRIMARY KEY\n, part text NOT NULL\n, wide int NOT NULL\n, high int NOT NULL\n);\n\nCREATE TABLE board (\n board_id serial PRIMARY KEY\n, board text NOT NULL\n, wide int NOT NULL\n, high int NOT NULL\n);\n
Run Code Online (Sandbox Code Playgroud)\nIMMUTABLE
功能:
CREATE OR REPLACE FUNCTION f_boardbox(_board_id int)\n RETURNS box\n LANGUAGE sql IMMUTABLE AS\n\'SELECT box(point(0,0), point (b.wide, b.high))\n FROM public.board b WHERE board_id = $1\'; \n\nCREATE OR REPLACE FUNCTION f_partbox(_part_id int, _wide int, _high int)\n RETURNS box\n LANGUAGE sql IMMUTABLE AS\n\'SELECT box(point($2,$3), point($2 + p.wide, $3 + p.high))\n FROM public.part p WHERE part_id = $1\';\n
Run Code Online (Sandbox Code Playgroud)\n替换public
为表的实际架构。
\n在 Postgres 9.6 或更高版本中添加PARALLEL SAFE
. 看:
主表:
\nCREATE TABLE board_part (\n board_id int NOT NULL REFERENCES board\n, part_id int NOT NULL REFERENCES part\n, wide int NOT NULL\n, high int NOT NULL\n, EXCLUDE USING gist (board_id WITH =, f_partbox(part_id, wide, high) WITH &&)\n, CHECK (f_partbox(part_id, wide, high) <@ f_boardbox(board_id))\n);\n
Run Code Online (Sandbox Code Playgroud)\n当然,如果您更新 aboard_id
或part_id
正在使用的维度,则部分索引和/或约束无效。您需要重新创建任何索引或约束,其基础是以下承诺:IMMUTABLE
函数返回值永不改变的承诺的任何索引或约束。看:
但是,您可以使用触发器来避免这种昂贵的操作,以仅更新受影响的行:
\nCREATE OR REPLACE FUNCTION f_board_upaft()\n RETURNS trigger\n LANGUAGE plpgsql AS\n$func$\nBEGIN\n UPDATE board_part\n SET board_id = board_id -- enough to trigger CHECK constraint\n WHERE board_id = NEW.board_id; -- limit to relevant rows\n\n RETURN NULL;\nEND\n$func$;\n\nCREATE TRIGGER board_upaft\nAFTER UPDATE OF wide, high ON board -- limit to relevant columns\nFOR EACH ROW EXECUTE PROCEDURE f_board_upaft();\n\n\nCREATE OR REPLACE FUNCTION f_part_upaft()\n RETURNS trigger\n LANGUAGE plpgsql AS\n$func$\nBEGIN\n UPDATE board_part\n SET part_id = 0\n WHERE part_id = NEW.part_id;\n\n UPDATE board_part\n SET part_id = NEW.part_id\n WHERE part_id = 0; -- enough to update EXCL. constraint\n\n RETURN NULL;\nEND\n$func$;\n\nCREATE TRIGGER part_upaft\nAFTER UPDATE OF wide, high ON part\nFOR EACH ROW EXECUTE PROCEDURE f_part_upaft();\n
Run Code Online (Sandbox Code Playgroud)\npart_id = 0
是表中的特殊行part
。触发器需要。
测试数据:
\nINSERT INTO board (board, wide, high)\nVALUES (\'b1010\',10,10), (\'b2030\',20, 30);\n\nINSERT INTO part (part_id, part, wide, high)\nVALUES (0,\'p0\',0,0); -- special row needed for trigger!\n\nINSERT INTO part (part, wide, high)\nVALUES (\'p11\',1,1), (\'p33\',3,3);\n\nINSERT INTO board_part (board_id, part_id, wide, high)\nVALUES (1,1,3,3)\n , (1,1,5,5) -- no overlap, inside board\n , (2,1,3,3); -- different board, no overlap\n
Run Code Online (Sandbox Code Playgroud)\n如果触发器和约束完成其工作,这些必须引发异常:
\nUPDATE part SET wide = 6 , high = 6\nWHERE part_id = 1; -- violates EXCL. & CHECK constraint\n\nUPDATE part SET wide = 3 , high = 3\nWHERE part_id = 1; -- violates EXCL. constraint\n\nUPDATE board SET wide = 2 , high = 2\nWHERE board_id = 1; -- violates CHECK constraint\n
Run Code Online (Sandbox Code Playgroud)\n\n
归档时间: |
|
查看次数: |
529 次 |
最近记录: |