Как всегда запускать UPDATE FROM, несмотря на отсутствующие записи в исходной таблице? - PullRequest
0 голосов
/ 11 января 2019

Я подготовил упрощенный тестовый пример для моего вопроса -

screenshot

В 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 для каждого события входа пользователя в систему)

Ответы [ 3 ]

0 голосов
/ 11 января 2019

Вам не нужно дважды искать большой стол geoip:

-- start transaction
-- some stuff

UPDATE users u SET
    visited = now(),               
    ip      = '20.20.20.20'::inet 
WHERE u.uid = 2;  -- fast because is from pk

UPDATE users u SET
    lat     = i.lat,
    lng     = i.lng
FROM geoip i
WHERE u.uid = 2 AND '20.20.20.20'::inet <<= i.block;

-- more stuff
-- commit tx
0 голосов
/ 13 января 2019

г. Эндрю Гирт из списка рассылки pgsql-general предоставил ответ только для SQL:

UPDATE users u SET 
    visited = now(),
    ip = v.ip,
    lat = i.lat,
    lng = i.lng
FROM (VALUES ('20.20.20.20'::inet)) v(ip)
      LEFT JOIN geoip i ON (v.ip <<= i.block)
WHERE u.uid = 2;
0 голосов
/ 11 января 2019

Мое предложение (возможно, глупое) состоит в том, чтобы добавить u.uid = 2 OR (u.uid = 2 AND '20.20.20.20'::inet <<= i.block) вместо этого AND условия .. и, возможно, изменить lat = i.lat на lat = NULLIF(i.lat, 0)

...