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

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

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中,您可以利用检查约束和可延迟的外键约束来强制执行约束。

视图

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_idresource2_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 = &#39;Resource1&#39; and resource1_id is not null
                                              or resource_type = &#39;Resource2&#39; and resource2_id is not null
                                              )  
                                              
          );

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:

确定