User、Permission和多个资源之间的关系,无需中间表。

huangapple go评论108阅读模式
英文:

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*, ...)

Resource1Resource2 之间没有语义关联(独立)。如何强制我的数据库确保 Permission.resource_id 存在于 Resource1Resource2 中的一个且仅一个?

我可以使用一个中间表 Resource(id*),并在对 Resource1/2 进行插入操作时向该表添加记录,并将外键约束设置为 Permission.resource_idResource.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中,您可以利用检查约束和可延迟的外键约束来强制执行约束。

视图

  1. CREATE VIEW my_view AS
  2. SELECT id, 'Resource1' AS type FROM Resource1
  3. UNION ALL
  4. SELECT id, 'Resource2' AS type FROM Resource2;

权限表:

  1. CREATE TABLE Permission (
  2. user_id INTEGER,
  3. resource_id INTEGER,
  4. resource_type TEXT,
  5. role TEXT,
  6. -- 添加检查约束以确保 resource_type 要么是 'Resource1' 要么是 'Resource2'
  7. CHECK (resource_type IN ('Resource1', 'Resource2')),
  8. -- 添加一个带有可延迟选项的外键约束,以允许延迟检查
  9. FOREIGN KEY (resource_id, resource_type) REFERENCES my_view (id) DEFERRABLE INITIALLY DEFERRED
  10. );

触发器

  1. CREATE OR REPLACE FUNCTION check_foreign_id() RETURNS TRIGGER AS $$
  2. BEGIN
  3. -- 基于查询的所需条件
  4. IF NEW.foreign_id NOT IN (SELECT id FROM some_table WHERE condition = true) THEN
  5. RAISE EXCEPTION '违反外键约束。';
  6. END IF;
  7. RETURN NEW;
  8. END;
  9. $$ LANGUAGE plpgsql;
  10. CREATE TRIGGER check_foreign_id_trigger
  11. BEFORE INSERT OR UPDATE ON my_table
  12. 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

  1. CREATE VIEW my_view AS
  2. SELECT id, 'Resource1' AS type FROM Resource1
  3. UNION ALL
  4. SELECT id, 'Resource2' AS type FROM Resource2;

Permission table:

  1. CREATE TABLE Permission (
  2. user_id INTEGER,
  3. resource_id INTEGER,
  4. resource_type TEXT,
  5. role TEXT,
  6. -- Add a check constraint to ensure the resource_type is either 'Resource1' or 'Resource2'
  7. CHECK (resource_type IN ('Resource1', 'Resource2')),
  8. -- Add a foreign key constraint with deferrable option to allow for deferred checks
  9. FOREIGN KEY (resource_id, resource_type) my_view (id) DEFERRABLE INITIALLY DEFERRED
  10. );

Trigger

  1. CREATE OR REPLACE FUNCTION check_foreign_id() RETURNS TRIGGER AS $$
  2. BEGIN
  3. -- Your desired conditions based on the query
  4. IF NEW.foreign_id NOT IN (SELECT id FROM some_table WHERE condition = true) THEN
  5. RAISE EXCEPTION 'Foreign key constraint violation.';
  6. END IF;
  7. RETURN NEW;
  8. END;
  9. $$ LANGUAGE plpgsql;
  10. CREATE TRIGGER check_foreign_id_trigger
  11. BEFORE INSERT OR UPDATE ON my_table
  12. FOR EACH ROW EXECUTE FUNCTION check_foreign_id();

答案2

得分: 1

修改您的 Permission 表,包括 resource1_idresource2_id。然后添加一个 check constraint 来确保它们中的一个是 null。您还可以交叉检查 resource_type 与非 null 资源 id。<br/>

Sample table DDL with constraints.

  1. create table Permission(user_id integer not null
  2. references users(id)
  3. , resource1_id integer references resource1(id)
  4. , resource2_id integer references resource1(id)
  5. , resource_type text
  6. -- 确保 resource ids 中只有一个为 null
  7. , constraint exactly_one_resource check( num_nulls(resource1_id, resource2_id) = 1)
  8. -- null 资源 id 必须与 resource_type 匹配
  9. , constraint id_matches_type check ( resource_type = 'Resource1' and resource1_id is not null
  10. or resource_type = 'Resource2' and resource2_id is not null
  11. )
  12. );

这不需要任何触发器。

英文:

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.

  1. create table Permission(user_id integer not null
  2. references users(id)
  3. , resource1_id integer references resource1(id)
  4. , resource2_id integer references resource1(id)
  5. , resource_type text
  6. -- exactly 1 of resource ids must be null
  7. , constraint exactly_one_resource check( num_nulls(resource1_id, resource2_id) = 1)
  8. -- not null resource id must agree with resource_type
  9. , constraint id_matches_type check ( resource_type = &#39;Resource1&#39; and resource1_id is not null
  10. or resource_type = &#39;Resource2&#39; and resource2_id is not null
  11. )
  12. );

This does not require any triggers.

huangapple
  • 本文由 发表于 2023年7月6日 18:43:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627982.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定