约束,基于与另一个表的连接

Den*_*nov 3 postgresql

我有表tariffs,有两列:(tariff_id, reception)

我有表users,有两列:(user_id, reception)

我有一个users_tariffs包含两列的表:(user_id, tariff_id)

我想防止将一个接收处的资费分配给另一接收处的用户的情况。我怎样才能做到这一点?

例如

用户:

user_id | reception
Putin   | Russia
Trump   | USA
Run Code Online (Sandbox Code Playgroud)

关税:

tariff_id | reception
cheap     | USA
expensive | Russia
Run Code Online (Sandbox Code Playgroud)

users_tariffs 的情况错误,因为廉价关税仅适用于美国:

user_id | tariff_id
Putin   | Cheap
Run Code Online (Sandbox Code Playgroud)

flu*_*ter 5

解决方案 1:外键约束

我假设下表定义。特别是, 中的复合键使和user_tariffs之间形成多对多关系。userstariffs

CREATE TABLE tariffs (tariff_id int NOT NULL PRIMARY KEY,  
                      reception text NOT NULL);  
CREATE TABLE users (user_id int NOT NULL PRIMARY KEY,  
                    reception text NOT NULL);  
CREATE TABLE user_tariffs (tariff_id int NOT NULL REFERENCES tariffs (tariff_id),  
                           user_id int NOT NULL REFERENCES users (user_id),  
                           PRIMARY KEY (tariff_id, user_id));  
Run Code Online (Sandbox Code Playgroud)

您可能需要在某个地方组合所有三列,所以让我们创建这个:

ALTER TABLE user_tariffs ADD COLUMN reception text;  
UPDATE user_tariffs a  
SET reception = b.reception  
FROM (SELECT * FROM tariffs) b  
WHERE a.tariff_id = b.tariff_id;  
ALTER TABLE user_tariffs ALTER COLUMN reception SET NOT NULL;  
Run Code Online (Sandbox Code Playgroud)

现在我们可以(user_id, reception)users.

CREATE UNIQUE INDEX ON tariffs (tariff_id, reception);  
ALTER TABLE user_tariffs ADD FOREIGN KEY (tariff_id, reception)  
                      REFERENCES tariffs (tariff_id, reception);  
Run Code Online (Sandbox Code Playgroud)

此外,我们可以使用 FK REF(tariff_id, reception)tariffs.

CREATE UNIQUE INDEX ON users (user_id, reception);  
ALTER TABLE user_tariffs ADD FOREIGN KEY (user_id, reception) 
                        REFERENCES users (user_id, reception);  
Run Code Online (Sandbox Code Playgroud)

填充数据:

INSERT INTO users VALUES (1, 'cheap'), (2, 'expensive');
INSERT INTO tariffs VALUES (1, 'cheap'), (2, 'expensive');
Run Code Online (Sandbox Code Playgroud)

现在假设我们有以下数据(user_id, tariff_id)要插入:

WITH data (user_id, tariff_id) 
       AS (VALUES (1, 2), (2, 1)),   -- here is your application data
     datas (user_id, tariff_id, reception) 
       AS (SELECT user_id, 
                  tariff_id, 
                  (SELECT u.reception  -- reception calculated by user
                   FROM users u 
                   WHERE u.user_id = d.user_id)  
           FROM data d) 
INSERT INTO user_tariffs SELECT * FROM datas ;
Run Code Online (Sandbox Code Playgroud)

那么你就无法插入数据,因为你只能添加相同的(1, 1)or ,而不能添加不同的or 。错误信息是:(2, 2)reception(1, 2)(2, 1)reception

ERROR:  insert or update on table "user_tariffs" violates foreign key constraint "user_tariffs_user_id_fkey1"
DETAIL:  Key (user_id, reception)=(2, cheap) is not present in table "users".
Run Code Online (Sandbox Code Playgroud)

但你可以插入data AS VALUES (1, 1), (2, 2). 我认为 FOREIGN KEY CONSTRAINT 解决方案是首选。

如果您想要更好的桌子设计,请描述您的functional dependencies

解决方案 2:触发

-- DROP TABLE user_tariffs CASCADE;  
-- DROP TABLE users CASCADE;  
-- DROP TABLE tariffs CASCADE;  
CREATE TABLE tariffs (tariff_id int NOT NULL PRIMARY KEY,  
                      reception text NOT NULL);  
CREATE TABLE users (user_id int NOT NULL PRIMARY KEY,  
                    reception text NOT NULL);  
CREATE TABLE user_tariffs (tariff_id int NOT NULL REFERENCES tariffs (tariff_id),  
                           user_id int NOT NULL REFERENCES users (user_id),  
                           PRIMARY KEY (tariff_id, user_id));  
INSERT INTO users VALUES (1, 'cheap'), (2, 'expensive');  
INSERT INTO tariffs VALUES (1, 'cheap'), (2, 'expensive');  
-- table user_tariffs (user_id, tariff_id) only, without reception column.
Run Code Online (Sandbox Code Playgroud)

创建一个具有返回类型触发器的函数:

CREATE OR REPLACE FUNCTION check_reception()  
RETURNS trigger AS $$
DECLARE valid boolean := false;
BEGIN 
SELECT (SELECT u.reception FROM users u WHERE u.user_id = NEW.user_id) 
     = (SELECT t.reception FROM tariffs t WHERE t.tariff_id = NEW.tariff_id) 
INTO valid FROM user_tariffs ;
IF valid = false  
THEN RAISE EXCEPTION '(user, tariff, reception) invalid.';  
END IF;  
RETURN NEW;
END; $$ LANGUAGE plpgsql ;
Run Code Online (Sandbox Code Playgroud)

并注册它:

CREATE TRIGGER reception_trigger  
AFTER INSERT OR UPDATE ON user_tariffs  
FOR EACH ROW EXECUTE PROCEDURE check_reception();
Run Code Online (Sandbox Code Playgroud)

现在尝试插入 (1, 2),这将是(便宜的,昂贵的)并且是不允许的:

INSERT INTO user_tariffs VALUES (1, 2);
ERROR:  (user, tariff, reception) invalid.
KONTEXT:  PL/pgSQL function check_reception() line 7 at RAISE
Run Code Online (Sandbox Code Playgroud)

但我们可以插入 (1, 1),这是(便宜的,便宜的)没有问题:

INSERT INTO user_tariffs VALUES (1, 1);
SELECT * FROM user_tariffs;  
Run Code Online (Sandbox Code Playgroud)

评论

在我看来,触发器并不是最好的解决方案。如果可能的话,尽量避免触发因素。它们可能会产生副作用(交易等)。检查 StackOverflow 了解更多详细信息:)