我有一个例子,我想要进行一个复杂的约束检查,其中我的表中引用的外键会根据该表中的另一个值而变化。这可能不是最好的实现,但由于科技债务太多,我无法改变它。目前我们没有约束检查,这可能会导致问题,我的目的是解决这个问题,而不是重做整个数据库,即使这是理想的。
以下是我的案例的摘要:
CREATE TABLE VehicleType (
vehicletypeid integer primary key,
vehicletypename varchar not null,
pktablename varchar,
pkcolumnname varchar
);
CREATE TABLE Car (carid integer primary key, carname varchar);
CREATE TABLE Truck (truckid integer primary key, truckname varchar);
CREATE TABLE VehicleColourOptions (
vehicletypeid integer REFERENCES VehicleType(vehicletypeid),
vehicleid integer,
colourid integer
); /* colourid would have an fk reference in my use case */
INSERT INTO VehicleType (vehicletypeid, vehicletypename, pktablename, pkcolumnname) VALUES
(1, 'Car', 'Car', 'carid'), (2, 'Truck', 'Truck', 'truckid');
也可能有两种以上的类型。
之后,我当前的解决方案是创建一个函数和触发器,该函数和触发器将检查引用的vehicleid
是否作为主键存在于Car
或Truck
中,具体取决于提供的vehicletypeid
。这些可能有些类似:
CREATE OR REPLACE FUNCTION check_vehicle_has_pk()
RETURNS trigger LANGUAGE 'plpgsql' AS $$
DECLARE
tablename varchar;
columnname varchar;
typename varchar;
key_count integer;
vehicleid integer;
BEGIN
SELECT pktablename, pkcolumnname, vehicletypename
INTO tablename, columnname, typename
FROM VehicleType
WHERE NEW.vehicletypeid = vehicletypeid;
IF tablename IS NOT NULL AND columnname IS NOT NULL THEN
vehicleid := NEW.vehicleid;
EXECUTE format('
SELECT count(*)
FROM %s
WHERE %s.%s = %s;
', tablename, tablename, columnname, vehicleid)
INTO key_count;
IF key_count = 0 THEN
RAISE EXCEPTION 'referenced vehicle type % does not have corresponding id %', typename, NEW.vehicleid;
IF TG_OP = 'INSERT' THEN
RETURN NULL;
ELSE
RETURN OLD;
END IF;
ELSE
RETURN NEW;
END IF;
END IF;
END;$$;
CREATE CONSTRAINT TRIGGER VehicleColourOptionsInsertOrUpdate
AFTER INSERT OR UPDATE
ON VehicleColourOptions
FOR EACH ROW
EXECUTE PROCEDURE check_vehicle_has_pk();
因此,我们在VehicleColourOptions
中的INSERT或UPDATE期间进行条件检查,以确定事务是否会继续。
但是逆约束不存在,也就是说,如果引用的VehicleType
的表中不存在对应的pk,则不能在VehicleColourOptions
中创建或更新值,但是,如果VehicleColourOptions
引用的值在其对应表中被删除,则默认情况下不会进行相应的检查。例如,以下删除应该是不可能的,但可以:
INSERT INTO Car
(carid, carname)
VALUES
(1, 'big car');
INSERT INTO VehicleColourOptions
(vehicletypeid, vehicleid, colourid)
VALUES
(1, 1, 1);
DELETE FROM Car WHERE carid = 1;
我想弄清楚的是如何实现反向约束,或者是否存在比我所做的更好的解决方案(假设我不能更改表或列)。我想到的解决方案是在作为车辆类型表的每个表上创建反向删除CONSTRAINT TRIGGER
。问题是,我有大约20个表引用了“VehicleType
”和~50“VehicleType
s”,每个表大约有1000行可以被引用(提醒一下,这是我的实际数据库的抽象)。这意味着,如果我对每个表都这样做,我将不得不对INSERT和UPDATE进行大约20次检查(我对此很满意),但然后我将需要创建大约50个删除触发器,并且每个触发器在删除期间都需要检查大约20个表。这看起来是这样的:
CREATE OR REPLACE FUNCTION check_pk_not_referenced_as_vehicleid()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $$
DECLARE
oldvehicletypeid integer := TG_ARGV[0]::integer;
oldvehicleid integer;
columnname varchar;
checktables varchar[] := ARRAY['VehicleColourOptions']::varchar[];
tablewithmatch varchar[] := ARRAY[]::varchar[];
t_iter varchar;
ref_count integer;
BEGIN
IF TG_OP = 'INSERT' THEN RETURN NEW;
END IF;
SELECT pkcolumnname
INTO columnname
FROM VehicleType
WHERE VehicleType.vehicletypeid = oldvehicletypeid;
EXECUTE format('SELECT ($1).%s;', columnname)
USING OLD
INTO oldvehicleid;
FOREACH t_iter IN ARRAY checktables LOOP
EXECUTE format('
SELECT count(*)
FROM %s
WHERE vehicletypeid = %s
AND vehicleid = %s;
', t_iter, oldvehicletypeid, oldvehicleid)
INTO ref_count;
IF ref_count > 0 THEN
tablewithmatch := ARRAY_APPEND(tablewithmatch, t_iter);
END IF;
END LOOP;
IF (SELECT count(*) FROM UNNEST(tablewithmatch)) > 0 THEN
RAISE EXCEPTION 'vehicletypeid % with vehicleid % cannot be deleted because it is referenced in tables: %', oldvehicletypeid, oldvehicleid, array_to_string(tablewithmatch, ',');
RETURN OLD;
ELSE
RETURN NULL;
END IF;
END;$$;
CREATE CONSTRAINT TRIGGER CarUpdateOrDelete
AFTER UPDATE OR DELETE
ON Car
FOR EACH ROW
EXECUTE PROCEDURE check_pk_not_referenced_as_vehicleid(1);
CREATE CONSTRAINT TRIGGER TruckUpdateOrDelete
AFTER UPDATE OR DELETE
ON Truck
FOR EACH ROW
EXECUTE PROCEDURE check_pk_not_referenced_as_vehicleid(2);
是否有其他方法可以更好地为变量FK关系复制FK约束?
附带问题
除了不能使用FK约束之外,表不应该以这种方式构建的好理由是什么?我可以想到优点,主要的优点是可扩展性,因此用户可以将任何类型与任何类型相关联。我上面的抽象没有实现这个用例,因为人们可能没有用例来关联轿车和卡车,但在我的工作中,类型概念用于许多不同的事情,从词汇表到位置再到对象,我们有许多不同的关联表。
还有一个好处是,后端代码可以非常简单。如果我想要一个用户界面来将车辆与颜色选项相关联,我只需要提供一个后端服务,该服务将从VehicleColourOptions表中删除。如果我有一个CarColourOptions和TruckColourOptions表,这就变得更复杂了。并不是我不愿意投入这项工作,但当我在写后台时,这是很好的。
1条答案
按热度按时间jtw3ybtb1#
我完全不确定您要做什么,但我猜您正在尝试实现某种“子类型”?如果是这种情况,典型的模式是:
现在,FK FK_Vehicle_type可确保汽车具有与父车辆相同的Vehicle_type,而检查约束ck_car可确保只有汽车才能具有类型Car。