英文:
Relationship between User, Permission and multiple resources without intermediate table
问题
我有4个实体:
User(id*, ...)
Permission(user_id*, resource_id*, resource_type, role)
Resource1(id*, ...)
Resource2(id*, ...)
Resource1
和 Resource2
之间没有语义关联(独立)。如何强制我的数据库确保 Permission.resource_id
存在于 Resource1
和 Resource2
中的一个且仅一个?
我可以使用一个中间表 Resource(id*)
,并在对 Resource1/2 进行插入操作时向该表添加记录,并将外键约束设置为 Permission.resource_id
到 Resource.id
。但如何在没有 Resource
表的情况下实现这个逻辑呢?我更喜欢在 SQLite 或 PostgreSQL 中使用,但如果在任何其他 SQL 数据库引擎中提供解决方案,我也会满意。
英文:
I have 4 entities :
User(id*, ...)
Permission(user_id*, resource_id*, resource_type, role)
Resource1(id*, ...)
Resource2(id*, ...)
Resource1
and Resource2
share no semantic relation (are independent). How can I force my database to ensure Permission.resource_id
exists in one and exactly one of Resource1
and Resource2
?
I can use an intermediate table Resource(id*)
and add a procedure to add the record into that table upon an INSERT statement on Resource1/2 and set a foreign key constraint on Permission.resource_id
to Resource.id
. But how to implement that logic without the Resource
table? I would prefer to use SQLite or PostgreSQL, but I'm happy with a solution in any other SQL database engine.
答案1
得分: 1
在PostgreSQL
中,您可以利用检查约束和可延迟的外键约束来强制执行约束。
视图
CREATE VIEW my_view AS
SELECT id, 'Resource1' AS type FROM Resource1
UNION ALL
SELECT id, 'Resource2' AS type FROM Resource2;
权限表:
CREATE TABLE Permission (
user_id INTEGER,
resource_id INTEGER,
resource_type TEXT,
role TEXT,
-- 添加检查约束以确保 resource_type 要么是 'Resource1' 要么是 'Resource2'
CHECK (resource_type IN ('Resource1', 'Resource2')),
-- 添加一个带有可延迟选项的外键约束,以允许延迟检查
FOREIGN KEY (resource_id, resource_type) REFERENCES my_view (id) DEFERRABLE INITIALLY DEFERRED
);
触发器
CREATE OR REPLACE FUNCTION check_foreign_id() RETURNS TRIGGER AS $$
BEGIN
-- 基于查询的所需条件
IF NEW.foreign_id NOT IN (SELECT id FROM some_table WHERE condition = true) THEN
RAISE EXCEPTION '违反外键约束。';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_foreign_id_trigger
BEFORE INSERT OR UPDATE ON my_table
FOR EACH ROW EXECUTE FUNCTION check_foreign_id();
英文:
In PostgreSQL
, you can utilize check constraints along with a deferrable foreign key constraint to enforce the constraint.
View
CREATE VIEW my_view AS
SELECT id, 'Resource1' AS type FROM Resource1
UNION ALL
SELECT id, 'Resource2' AS type FROM Resource2;
Permission table:
CREATE TABLE Permission (
user_id INTEGER,
resource_id INTEGER,
resource_type TEXT,
role TEXT,
-- Add a check constraint to ensure the resource_type is either 'Resource1' or 'Resource2'
CHECK (resource_type IN ('Resource1', 'Resource2')),
-- Add a foreign key constraint with deferrable option to allow for deferred checks
FOREIGN KEY (resource_id, resource_type) my_view (id) DEFERRABLE INITIALLY DEFERRED
);
Trigger
CREATE OR REPLACE FUNCTION check_foreign_id() RETURNS TRIGGER AS $$
BEGIN
-- Your desired conditions based on the query
IF NEW.foreign_id NOT IN (SELECT id FROM some_table WHERE condition = true) THEN
RAISE EXCEPTION 'Foreign key constraint violation.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_foreign_id_trigger
BEFORE INSERT OR UPDATE ON my_table
FOR EACH ROW EXECUTE FUNCTION check_foreign_id();
答案2
得分: 1
修改您的 Permission
表,包括 resource1_id
和 resource2_id
。然后添加一个 check constraint 来确保它们中的一个是 null。您还可以交叉检查 resource_type
与非 null 资源 id。<br/>
Sample table DDL with constraints.
create table Permission(user_id integer not null
references users(id)
, resource1_id integer references resource1(id)
, resource2_id integer references resource1(id)
, resource_type text
-- 确保 resource ids 中只有一个为 null
, constraint exactly_one_resource check( num_nulls(resource1_id, resource2_id) = 1)
-- 非 null 资源 id 必须与 resource_type 匹配
, constraint id_matches_type check ( resource_type = 'Resource1' and resource1_id is not null
or resource_type = 'Resource2' and resource2_id is not null
)
);
这不需要任何触发器。
英文:
Modify your Permission
table to include both resource1_id
and resource2_id
. Then add a check constraint to make sure exactly 1 of them is null. You can also cross check resource_type
vs. the non-null resource id. <br/>
Permission(user_id*, resource1_id*, resource2_id* resource_type, role);
Sample table DDL with constraints.
create table Permission(user_id integer not null
references users(id)
, resource1_id integer references resource1(id)
, resource2_id integer references resource1(id)
, resource_type text
-- exactly 1 of resource ids must be null
, constraint exactly_one_resource check( num_nulls(resource1_id, resource2_id) = 1)
-- not null resource id must agree with resource_type
, constraint id_matches_type check ( resource_type = 'Resource1' and resource1_id is not null
or resource_type = 'Resource2' and resource2_id is not null
)
);
This does not require any triggers.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论