REFERENCES 两个不同表中的两个不同列

REFERENCES two different columns in two different tables

在 Postgresql 数据库中,我们有一个 table 如下所示:

=# SELECT * FROM toy_cars;
  serial_no |     name
------------+---------------
 199ER276FN | Snow Doctor
 8BE0F79A3R | Flatbed Truck
 D76185CE8G | Sand Speeder

=# SELECT * FROM toy_trains;
    serial_no    |    name
-----------------+-------------
 BMXH5R4T8K7KELD | Howler T140
 B1Q1JJDQW9LQN0G | Quakester
 8HO9240TO6RNNQ9 | Medusa 90

=# SELECT * FROM items_for_sale;
    serial_no    |   in_stock
-----------------+---------------
      199ER276FN | t
 BMXH5R4T8K7KELD | t
 B1Q1JJDQW9LQN0G | f
      8BE0F79A3R | f
 8HO9240TO6RNNQ9 | t
      D76185CE8G | f

注:

我们想在 items_for_sale table 中对 serial_no 添加 REFERENCES 检查,以确保输入的序列号存在于 toy_cars table 或 toy_trains table.

因此,如果我要尝试 INSERT INTO items_for_sale VALUES('KYVGK0DBYXPMWW8','f'); 这将失败,因为该序列号不存在于 toy_carstoy_trains.

如何做到这一点?我们更喜欢使用一个 table(就像现在的结构一样)。

这里的问题是您想要检查两个(其他)table 中是否存在一个键,并且您不能在单个列上使用外键约束来强制执行该操作。 Postgres 允许您为自定义检查创建 CHECK 表达式,但作为 the manual says:

PostgreSQL does not support CHECK constraints that reference table data other than the new or updated row being checked. (...) If possible, use UNIQUE, EXCLUDE, or FOREIGN KEY constraints to express cross-row and cross-table restrictions.

If what you desire is a one-time check against other rows at row insertion, rather than a continuously-maintained consistency guarantee, a custom trigger can be used to implement that.

请参阅 this very related question 了解不同的解决方案。

某些数据库(MS SQL 服务器)允许您use a function in CHECK expressions。那将是最佳选择,但 Postgres 不允许该语法。

对于 PostgreSQL,您需要创建一个 TRIGGER,它将在某些内容插入 items_for_sale 时执行。另一方面,确实允许函数或交叉table检查。

看起来像这样:

CREATE TRIGGER check_serial_present
    BEFORE INSERT ON items_for_sale
    FOR EACH ROW
    EXECUTE FUNCTION assert_presence_of_serial(); -- implement this function

另一个链接的问题也提到了一种无需触发器即可实现此目的的非常优雅的方法:

A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints

我非常喜欢它,因为它在概念上非常简单且易于掌握。我认为它只涉及这些步骤:

  1. ALTER TABLE 添加两列:toy_car_serialtoy_trains_serial,均为 NULLABLE,但对上述 table 具有 FOREIGN KEY 约束。
  2. 确保任何 INSERT 都会将序列号插入 serial_notoy_car_serialserial_notoy_car_serial.
  3. 添加CHECK( toy_car_serial = serial_no OR toy_trains_serial = serial_no).

我认为两个冗余行和对您的插入内容的轻微修改比替代方法要少得多。