web-dev-qa-db-fra.com

Postgres INSERT ON CONFLIT DO UPDATE vs INSERT ou UPDATE

J'ai stock_price_alert table avec 3 colonnes. stock_price_id est PRIMARY KEY & aussi FOREIGN KEY à une autre table. Définition du tableau ci-dessous:

create table stock_price_alert (
    stock_price_id integer references stock_price (id) on delete cascade not null,
    fall_below_alert boolean not null,
    rise_above_alert boolean not null,
    primary key (stock_price_id)
);

Je dois soit:

1) enregistrement INSERT s'il n'existe pas

-- query 1
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false);

2) enregistrement UPDATE s'il existe

-- query 2
UPDATE stock_price_alert SET
    fall_below_alert = true,
    rise_above_alert = false
WHERE stock_price_id = 1;

Je dois d’abord émettre une requête SELECT sur la table stock_price_alert, afin de décider d’effectuer une requête (1) ou (2).

Postgres prend en charge INSERT INTO TABLE .... ON CONFLICT DO UPDATE ...:

-- query 3
INSERT INTO stock_price_alert (stock_price_id, fall_below_alert, rise_above_alert)
VALUES (1, true, false)
ON CONFLICT (stock_price_id) DO UPDATE SET
    fall_below_alert = EXCLUDED.fall_below_alert,
    rise_above_alert = EXCLUDED.rise_above_alert;

Au lieu d'utiliser query (1) ou (2), puis-je toujours utiliser query (3)? Dans ce cas, je n'ai pas besoin d'émettre la requête SELECT dans previous & cela aide à simplifier le code. 

Mais je me demande quelle est la meilleure pratique? Query (3) causera-t-il un problème de performance ou des effets secondaires indésirables? Merci.

6
Shuwn Yuan Tee

La requête 3 est la syntaxe Postgres pour "UPSERT" (= UPDATE ou INSERT), introduite dans Postgres 9.5.

De la documentation :

ON CONFLICT DO UPDATE garantit un résultat atomique INSERT ou UPDATE; à condition qu'il n'y ait pas d'erreur indépendante, l'un de ces deux résultats est garanti, même sous forte concurrence. Ceci est également appelé UPSERT - “UPDATE ou INSERT”.

C’est la meilleure pratique pour ce que vous essayez d’atteindre.

6
Yoni Rabinovitch

J'ai remarqué/testé qu'il est beaucoup plus rapide pour les INSERTS (qui n'ont pas encore testé UPSERTS) d'utiliser un WHERE NOT EXISTS en plus de ON CONFLICT. Généralement, il est environ 3 fois plus rapide que d’autoriser ON CONFLICT à gérer les vérifications d’existence. Je pense que cela pourrait se prolonger dans UPSERTS, ce qui rendrait probablement plus rapide la réalisation d'une insertion, puis d'une mise à jour. Voici mon test pour les inserts uniquement ...

    --so i can keep rerunning
    DROP TABLE if exists temp1;
    DROP TABLE if exists temp2;

    --create a billion rows
    SELECT GENERATE_SERIES AS id INTO TEMP temp1
    FROM GENERATE_SERIES(1, 10000000);

    CREATE UNIQUE INDEX ux_id  ON temp1(id);
    ALTER TABLE temp1 CLUSTER ON ux_id;

    --create a second table to insert from, with the same data
    SELECT * INTO TEMP temp2 
    FROM temp1;

    CREATE UNIQUE INDEX ux_id2  ON temp2(id);
    ALTER TABLE temp2 CLUSTER ON ux_id2;

    --test inserting with on conflict only
    INSERT INTO temp1(id)
    SELECT id
    FROM temp2 ON conflict DO nothing;
    --execution time: 14.71s (1million rows)

    --test inserting with not exists and on conflict
    INSERT INTO temp1(id)
    SELECT t2.id
    FROM temp2 t2
    WHERE NOT EXISTS (SELECT 1 FROM temp1 t1 WHERE t2.id = t1.id) 
    ON conflict DO nothing;
    --execution time: 5.78s (1million rows)
1
Jeremy Giaco