Я подготовил упрощенный тестовый пример для моего вопроса -
В PostgreSQL 10.6 есть 2 таблицы:
CREATE TABLE users (
uid SERIAL PRIMARY KEY,
created timestamptz NOT NULL,
visited timestamptz NOT NULL,
ip inet NOT NULL,
lat double precision,
lng double precision
);
CREATE TABLE geoip (
block inet PRIMARY KEY,
lat double precision,
lng double precision
);
CREATE INDEX ON geoip USING SPGIST (block);
, заполненные следующими данными испытаний:
INSERT INTO users (created, visited, ip) VALUES
(now(), now(), '1.2.3.4'::inet),
(now(), now(), '1.2.3.5'::inet),
(now(), now(), '1.2.3.6'::inet);
INSERT INTO geoip (block, lat, lng) VALUES
('1.2.3.0/24', -33.4940, 143.2104),
('10.0.0.0/8', 34.6617, 133.9350);
Затем в сохраненной функции я запускаю следующую команду UPDATE -
UPDATE users u SET
visited = now(),
ip = '10.10.10.10'::inet,
lat = i.lat,
lng = i.lng
FROM geoip i
WHERE u.uid = 1 AND '10.10.10.10'::inet <<= i.block;
(1 и ip-адрес фактически являются параметрами in_uid
и in_ip
в моей сохраненной функции).
Приведенный выше запрос работает хорошо и обновляет все 4 поля в таблице users
.
Однако следующий запрос не работает должным образом и не обновляет никакие поля, поскольку в найденной таблице geoip
нет соответствующих block
:
UPDATE users u SET
visited = now(), -- HOW TO ALWAYS UPDATE THIS FIELD?
ip = '20.20.20.20'::inet, -- HOW TO ALWAYS UPDATE THIS FIELD?
lat = i.lat,
lng = i.lng
FROM geoip i
WHERE u.uid = 2 AND '20.20.20.20'::inet <<= i.block;
Однако поля visited
и ip
должны всегда обновляться - независимо от того, был найден block
или нет.
Вид LEFT JOIN, но для ОБНОВЛЕНИЯ - как этого добиться, пожалуйста?
Единственный обходной путь, о котором я мог подумать, это -
UPDATE users SET
visited = now(),
ip = '20.20.20.20'::inet,
lat = (SELECT lat FROM geoip WHERE '20.20.20.20'::inet <<= block),
lng = (SELECT lng FROM geoip WHERE '20.20.20.20'::inet <<= block)
WHERE uid = 2;
Но это будет запускать один и тот же подзапрос дважды (правильно?), И моя таблица geoip
уже работает медленно с 3073410 записями (и именно поэтому я пытаюсь кэшировать его lat
и lng
значения в таблице users
для каждого события входа пользователя в систему)