You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无中间表时,如何确保Permission的resource_id仅存在于Resource1或Resource2

无中间表时约束Permission.resource_id的数据库实现方案

针对需求——保证Permission.resource_id必须存在且仅存在于Resource1或Resource2其中一个表中,且不使用中间Resource表,以下是不同数据库的实现方案:

PostgreSQL 实现方案

PostgreSQL支持在CHECK约束中使用子查询,同时也支持分区表特性,两种方式都能满足需求:

方式1:CHECK约束结合子查询

直接在Permission表上添加CHECK约束,验证resource_id的有效性:

CREATE TABLE User (
    id INT PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Resource1 (
    id INT PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Resource2 (
    id INT PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Permission (
    user_id INT REFERENCES User(id),
    resource_id INT,
    resource_type VARCHAR(20) CHECK (resource_type IN ('resource1', 'resource2')),
    role VARCHAR(20),
    PRIMARY KEY (user_id, resource_id),
    -- 约束:resource_id必须存在于对应类型的资源表
    CONSTRAINT chk_resource_exists CHECK (
        (resource_type = 'resource1' AND EXISTS (SELECT 1 FROM Resource1 WHERE id = resource_id))
        OR
        (resource_type = 'resource2' AND EXISTS (SELECT 1 FROM Resource2 WHERE id = resource_id))
    ),
    -- 可选约束:禁止resource_id同时存在于两个资源表
    CONSTRAINT chk_resource_unique CHECK (
        NOT (
            EXISTS (SELECT 1 FROM Resource1 WHERE id = resource_id)
            AND
            EXISTS (SELECT 1 FROM Resource2 WHERE id = resource_id)
        )
    )
);

这种方式靠约束直接校验,无需额外触发器。如果业务允许同一个resource_id同时出现在两个资源表,可以去掉第二个CHECK约束。

方式2:分区表实现

把Permission表按resource_type分成两个分区,每个分区单独设置外键关联对应资源表:

-- 创建父表
CREATE TABLE Permission (
    user_id INT REFERENCES User(id),
    resource_id INT,
    resource_type VARCHAR(20) CHECK (resource_type IN ('resource1', 'resource2')),
    role VARCHAR(20),
    PRIMARY KEY (user_id, resource_id, resource_type)
) PARTITION BY LIST (resource_type);

-- 关联Resource1的分区
CREATE TABLE Permission_resource1 PARTITION OF Permission
FOR VALUES IN ('resource1')
CONSTRAINT fk_perm_res1 FOREIGN KEY (resource_id) REFERENCES Resource1(id);

-- 关联Resource2的分区
CREATE TABLE Permission_resource2 PARTITION OF Permission
FOR VALUES IN ('resource2')
CONSTRAINT fk_perm_res2 FOREIGN KEY (resource_id) REFERENCES Resource2(id);

分区表会自动把不同类型的权限记录分到对应分区,外键约束天然保证resource_id只对应正确的资源表,逻辑更清晰。

SQLite 实现方案

SQLite的CHECK约束不支持子查询,所以需要用触发器来实现校验逻辑:

步骤1:创建基础表

CREATE TABLE User (
    id INTEGER PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Resource1 (
    id INTEGER PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Resource2 (
    id INTEGER PRIMARY KEY,
    -- 其他业务字段
);

CREATE TABLE Permission (
    user_id INTEGER REFERENCES User(id),
    resource_id INTEGER,
    resource_type TEXT CHECK (resource_type IN ('resource1', 'resource2')),
    role TEXT,
    PRIMARY KEY (user_id, resource_id)
);

步骤2:添加触发器校验

-- 插入前校验
CREATE TRIGGER trg_permission_insert_check
BEFORE INSERT ON Permission
FOR EACH ROW
BEGIN
    SELECT CASE
        WHEN NEW.resource_type = 'resource1' AND NOT EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'Resource1中不存在该resource_id')
        WHEN NEW.resource_type = 'resource2' AND NOT EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'Resource2中不存在该resource_id')
        WHEN EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id) AND EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'resource_id同时存在于Resource1和Resource2中')
    END;
END;

-- 更新前校验
CREATE TRIGGER trg_permission_update_check
BEFORE UPDATE ON Permission
FOR EACH ROW
BEGIN
    SELECT CASE
        WHEN NEW.resource_type = 'resource1' AND NOT EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'Resource1中不存在该resource_id')
        WHEN NEW.resource_type = 'resource2' AND NOT EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'Resource2中不存在该resource_id')
        WHEN EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id) AND EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id)
            THEN RAISE(ABORT, 'resource_id同时存在于Resource1和Resource2中')
    END;
END;

触发器会在插入或更新Permission记录前检查resource_id的有效性,不符合规则就终止操作并抛出提示。如果不需要限制同一个resource_id不能同时出现在两个资源表,可以删掉触发器中对应的CASE分支。

其他数据库(以MySQL为例)

MySQL 8.0.16之前的版本不支持生效的CHECK约束,所以同样需要用触发器实现:

-- 定义校验函数
DELIMITER //
CREATE FUNCTION check_resource_validity(resource_id INT, resource_type VARCHAR(20))
RETURNS BOOLEAN
DETERMINISTIC
BEGIN
    IF resource_type = 'resource1' THEN
        RETURN EXISTS (SELECT 1 FROM Resource1 WHERE id = resource_id);
    ELSEIF resource_type = 'resource2' THEN
        RETURN EXISTS (SELECT 1 FROM Resource2 WHERE id = resource_id);
    ELSE
        RETURN FALSE;
    END IF;
END //
DELIMITER ;

-- 插入触发器
DELIMITER //
CREATE TRIGGER trg_permission_insert_check
BEFORE INSERT ON Permission
FOR EACH ROW
BEGIN
    IF NOT check_resource_validity(NEW.resource_id, NEW.resource_type) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'resource_id不存在于对应资源表中';
    END IF;
    IF EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id) AND EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'resource_id同时存在于Resource1和Resource2中';
    END IF;
END //
DELIMITER ;

-- 更新触发器
DELIMITER //
CREATE TRIGGER trg_permission_update_check
BEFORE UPDATE ON Permission
FOR EACH ROW
BEGIN
    IF NOT check_resource_validity(NEW.resource_id, NEW.resource_type) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'resource_id不存在于对应资源表中';
    END IF;
    IF EXISTS (SELECT 1 FROM Resource1 WHERE id = NEW.resource_id) AND EXISTS (SELECT 1 FROM Resource2 WHERE id = NEW.resource_id) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'resource_id同时存在于Resource1和Resource2中';
    END IF;
END //
DELIMITER ;

内容的提问来源于stack exchange,提问作者mlisthenewcool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:15:02