Re: How to always run UPDATE FROM despite missing records in the source table?

From: Adrian Klaver <adrian(dot)klaver(at)aklaver(dot)com>
To: Alexander Farber <alexander(dot)farber(at)gmail(dot)com>, pgsql-general <pgsql-general(at)postgresql(dot)org>
Subject: Re: How to always run UPDATE FROM despite missing records in the source table?
Date: 2019-01-11 15:55:05
Message-ID: 1f962321-5973-fe09-e52f-182115296555@aklaver.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

On 1/11/19 4:50 AM, Alexander Farber wrote:
> Good afternoon
>
> I have prepared a simplified test case for my question:
> https://www.db-fiddle.com/f/22jfWnsvqD8hVeFPXsyLbV/0
>
> In PostgreSQL 10.6 there are 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);
>
> which are filled with the following test data:
>
> 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 <http://1.2.3.0/24>', -33.4940, 143.2104),
>  ('10.0.0.0/8 <http://10.0.0.0/8>', 34.6617, 133.9350);
>
> Then in a stored function I run the following UPDATE command -
>
> 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;
>
> (the 1 and the ip address are actually in_uid and in_ip parameters in my
> stored function).
>
> The above query works well and updates all 4 fields in the users table.
>
> However the following query does not work as intended and does not
> update any fields, because there is no matching block in the geoip table
> found:
>
> 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;
>
> The field visited and ip however should be always updated - regardless
> if the block was found or not.
>
> Kind of LEFT JOIN, but for UPDATE - how to achieve this please?
>
> The only workaround that I could think of is -
>
> 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;
>
> But that would run the same subquery twice (correct?) and my geoip table
> is already slow with 3073410 records (and that is why I am trying to
> cache its lat and lng values in the users table on each user login event)

Why not put a test for the block in the function and then use different
UPDATE's depending on the result?

Pseudo code:

IF ip IN block THEN
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;

ELSE

UPDATE users u SET
visited = now(),
ip = '20.20.20.20'::inet
FROM geoip i
WHERE u.uid = 2 ;

END IF;

>
> Regards
> Alex

--
Adrian Klaver
adrian(dot)klaver(at)aklaver(dot)com

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Andrew Gierth 2019-01-11 16:51:25 Re: How to always run UPDATE FROM despite missing records in the source table?
Previous Message Kevin Brannen 2019-01-11 15:42:50 RE: How to always run UPDATE FROM despite missing records in the source table?