J'ai préparé un cas de test simplifié pour ma question -
Dans PostgreSQL 10.6, il y a 2 tables:
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;
qui sont remplies avec les données de test suivantes:
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;
Ensuite, dans une fonction stockée, j'exécute la commande UPDATE suivante -
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;
(le 1 et l'adresse IP sont en fait in_uid et in_ip paramètres dans ma fonction stockée).
La requête ci-dessus fonctionne bien et met à jour les 4 champs dans le users code > table.
Cependant, la requête suivante ne fonctionne pas comme prévu et ne met à jour aucun champ, car il n'y a pas de bloc correspondant dans la table geoip trouvé:
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);
Le champ visité et ip cependant doit toujours être mis à jour d - peu importe si le bloc a été trouvé ou non.
Genre de LEFT JOIN, mais pour UPDATE - comment y parvenir s'il vous plaît?
La seule solution de contournement auquel je pourrais penser est -
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);
Mais cela exécuterait la même sous-requête deux fois (correct?) et ma table geoip est déjà lente avec 3073410 enregistrements (et c'est pourquoi j'essaie de mettre en cache ses valeurs lat et lng dans la table users sur chaque événement de connexion utilisateur)
3 Réponses :
Ma suggestion (peut-être stupide) est d'ajouter u.uid = 2 OR (u.uid = 2 AND '20 .20.20.20 ':: inet << = i.block) insted de cela AND condition .. et peut-être changer lat = i.lat en lat = NULLIF (i.lat, 0)
Vous n'avez pas besoin de rechercher deux fois la grande table 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
Cela fonctionnera sûrement, mais j'espérais y parvenir en une seule MISE À JOUR (abuser de la magie noire de PostgreSQL serait aussi bien pour moi)
Pourquoi? ___________
J'espère qu'une seule MISE À JOUR prendrait moins de temps que deux.
À mon avis, ce sont deux opérations différentes. De plus, la première requête n'a aucun coût (presque)
À mon avis, à l'intérieur d'une fonction, le coût de la première requête est négligeable.