尽管源 table 中缺少记录,如何始终 运行 更新?
How to always run UPDATE FROM despite missing records in the source table?
我已经准备好了 a simplified test case 我的问题 -
在 PostgreSQL 10.6 中有 2 个 tables:
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
参数)。
上述查询运行良好并更新了 users
table.
中的所有 4 个字段
但是,以下查询没有按预期工作,也没有更新任何字段,因为在找到的 geoip
table 中没有匹配的 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 - 请问如何实现?
我能想到的唯一解决方法是 -
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
table 已经很慢了 3073410 条记录(这就是为什么我试图 缓存它的lat
和lng
值在每个用户登录事件users
table中)
我的建议(也许是愚蠢的)是添加 u.uid = 2 OR (u.uid = 2 AND '20.20.20.20'::inet <<= i.block)
而不是 AND
条件..并且可能将 lat = i.lat
更改为 lat = NULLIF(i.lat, 0)
您不需要查找 geoip
大 table 两次:
-- 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
先生pgsql-general 邮件列表中的 Andrew Gierth 提供了仅 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;
我已经准备好了 a simplified test case 我的问题 -
在 PostgreSQL 10.6 中有 2 个 tables:
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
参数)。
上述查询运行良好并更新了 users
table.
但是,以下查询没有按预期工作,也没有更新任何字段,因为在找到的 geoip
table 中没有匹配的 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 - 请问如何实现?
我能想到的唯一解决方法是 -
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
table 已经很慢了 3073410 条记录(这就是为什么我试图 缓存它的lat
和lng
值在每个用户登录事件users
table中)
我的建议(也许是愚蠢的)是添加 u.uid = 2 OR (u.uid = 2 AND '20.20.20.20'::inet <<= i.block)
而不是 AND
条件..并且可能将 lat = i.lat
更改为 lat = NULLIF(i.lat, 0)
您不需要查找 geoip
大 table 两次:
-- 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
先生pgsql-general 邮件列表中的 Andrew Gierth 提供了仅 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;