POSTGRESQL:ON UPDATE CASCADE未按预期工作

时间:2016-07-30 16:18:55

标签: postgresql

我很难理解ON UPDATE CASCADE的所需行为 当一个字段可以为空并且它的引用被更新时。

在下面的表city和statistic的示例中,当我们有一个城市并且它的省没有值(NULL)时触发器ON UPDATE CASCADE无法正常工作,而我期望它更新省值统计数据。

当我们使用EXPLAIN在城市上运行更新时,我们可以很容易地看到这种行为。

注意:我使用的是PostgreSQL 9.4.8

创建表格以重现案例

CREATE TABLE city (
    id            BIGSERIAL PRIMARY KEY,
    name          VARCHAR(64) NOT NULL,
    country_id    BIGINT NOT NULL,
    province_id   BIGINT,

    CONSTRAINT city_city_country_province_un UNIQUE (id, country_id, province_id)
);

CREATE TABLE statistics (
    id            BIGSERIAL PRIMARY KEY,
    city_id       BIGINT NOT NULL,
    country_id    BIGINT NOT NULL,
    province_id   BIGINT,
    some_data     INTEGER NOT NULL DEFAULT 0,

    CONSTRAINT statistics_city_country_province_fk FOREIGN KEY (city_id, country_id, province_id) REFERENCES city (id, country_id, province_id) ON UPDATE CASCADE ON DELETE CASCADE,
    CONSTRAINT statistics_city_un UNIQUE (city_id)
);

填充表格

INSERT INTO city (name, country_id, province_id) VALUES ('SAO CARLOS', 1, 1);
INSERT INTO city (name, country_id, province_id) VALUES ('VATICAN CITY', 2, NULL);

INSERT INTO statistics (city_id, country_id, province_id) VALUES (1, 1, 1);
INSERT INTO statistics (city_id, country_id, province_id) VALUES (2, 1, NULL);

省份非空时更新城市

EXPLAIN ANALYZE VERBOSE UPDATE city SET province_id = 3 WHERE id = 1;

查询计划

Update on public.city  (cost=0.15..8.17 rows=1 width=168) (actual time=0.238..0.238 rows=0 loops=1)
->  Index Scan using city_city_country_province_un on public.city  (cost=0.15..8.17 rows=1 width=168) (actual time=0.046..0.048 rows=1 loops=1)
     Output: id, name, country_id, 3::bigint, ctid
     Index Cond: (city.id = 1)
Planning time: 0.510 ms
Trigger RI_ConstraintTrigger_a_41406 for constraint statistics_city_country_province_fk on city: time=0.792 calls=1
Trigger RI_ConstraintTrigger_c_41408 for constraint statistics_city_country_province_fk on statistics: time=0.296 calls=1
Execution time: 1.412 ms
更新后

城市

 id | city_id | country_id | province_id | some_data
----+---------+------------+-------------+-----------
  1 |       1 |          1 |           3 |         0
  2 |       2 |          2 |             |         0
更新后

统计信息

 id | city_id | country_id | province_id | some_data
----+---------+------------+-------------+-----------
  1 |       1 |          1 |           3 |         0
  2 |       2 |          2 |             |         0

省份为NULL时更新城市

EXPLAIN ANALYZE VERBOSE UPDATE city SET province_id = 7 WHERE id = 2;

查询计划

Update on public.city  (cost=0.15..8.17 rows=1 width=168) (actual time=0.170..0.170 rows=0 loops=1)
->  Index Scan using city_city_country_province_un on public.city  (cost=0.15..8.17 rows=1 width=168) (actual time=0.042..0.044 rows=1 loops=1)
     Output: id, name, country_id, 7::bigint, ctid
     Index Cond: (city.id = 2)
Planning time: 0.423 ms
Execution time: 0.225 ms
更新后

城市

 id |     name     | country_id | province_id
----+--------------+------------+-------------
  1 | SAO CARLOS   |          1 |           3
  2 | VATICAN CITY |          2 |           7
更新后

统计信息

 id | city_id | country_id | province_id | some_data
----+---------+------------+-------------+-----------
  1 |       1 |          1 |           3 |         0
  2 |       2 |          2 |             |         0

编辑:当字段为NULL时强制更新

创建一个函数,以便在旧值为NULL

时进行更新
CREATE OR REPLACE FUNCTION update_null_province_reference() RETURNS TRIGGER AS $$
BEGIN
    IF (OLD.province_id IS NULL AND NEW.province_id IS NOT NULL) THEN
        UPDATE statistics SET province_id = NEW.province_id WHERE city_id = NEW.id;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

在城市上创建触发器

CREATE TRIGGER update_null_province_reference
AFTER UPDATE ON city
FOR EACH ROW
EXECUTE PROCEDURE update_null_province_reference();

1 个答案:

答案 0 :(得分:4)

这里没有意外发生,实际上NULL值是原因。

执行以下UPDATE语句时:

UPDATE city SET province_id = 3 WHERE id = 1

Postgre检测到(id, country_id, province_id)表中city组合已发生变化,然后在statistics表中查找具有相同值的记录,在本例中为元组{{ 1}}。找到此记录,因此Postgres也会更新(1, 1, 1)表。

然而,当您执行此statistics语句时:

UPDATE

Postgres在记录中执行UPDATE city SET province_id = 7 WHERE id = 2 UPDATE元组的值为(id, country_id, province_id)。但Postgres无法验证(2, 2, NULL)表中是否存在匹配记录。其原因与statistics的含义有关。在关系代数中,NULL表示“未知”。因此,Postgres会看到NULL并放弃级联,因为它无法验证NULL表中的NULL province_id实际上是否与city值匹配NULL表。