2016-07-30 3 views
4

Мне сложно понять желаемое поведение ON UPDATE CASCADE , когда одно поле является нулевым и обновляется эта ссылка.POSTGRESQL: ON UPDATE CASCADE не работает должным образом

В следующем примере таблиц города и статистики, когда у нас есть город и его провинция не имеет значения (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); 

Обновление город, когда провинция NOT NULL

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

QUERY PLAN

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; 

QUERY PLAN

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 

EDIT: работа Arround для обновления, когда поле 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(); 
+0

Вы показали поведение, которое происходит, но вы не объяснили, что вы ожидаете *. Пожалуйста, сделай так. Объясните, что означает «не работает». – alzee

ответ

4

Здесь ничего неожиданного не происходит, и на самом деле значение NULL является причиной.

При выполнении следующей UPDATE заявления:

UPDATE city SET province_id = 3 WHERE id = 1 

Postgre обнаруживает, что (id, country_id, province_id) сочетания изменилось в city таблицы, а затем ищет запись с одинаковыми значениями в statistics таблицы, в этом случае кортеж (1, 1, 1). Эта запись найдена, и поэтому Postgres также обновляет таблицу statistics.

Однако, когда вы выполняете эту UPDATE заявление:

UPDATE city SET province_id = 7 WHERE id = 2 

Postgres выполняет UPDATE на запись с (id, country_id, province_id) кортеж имеет значения (2, 2, NULL). Но Postgres не может проверить, существует ли соответствующая запись в таблице statistics. Причина этого связана со значением NULL. В реляционной алгебре NULL означает «неизвестно». Поэтому Postgres видит NULL и отказывается от каскада, потому что он не может проверить, что NULLprovince_id в таблице city фактически соответствует значению NULL в таблице statistics.

Смежные вопросы