UPDATE
syntaxe vyžaduje explicitně pojmenovat cílové sloupce. Možné důvody, proč se tomu vyhnout:
- Máte mnoho sloupců a chcete pouze zkrátit syntaxi.
- To nevíte názvy sloupců kromě jedinečných sloupců.
"All columns"
musí znamenat "všechny sloupce cílové tabulky" (nebo alespoň "hlavní sloupce tabulky" ) v odpovídajícím pořadí a typu dat. Jinak byste stejně museli poskytnout seznam názvů cílových sloupců.
Testovací tabulka:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
1. DELETE
&INSERT
místo toho v jediném dotazu
Aniž byste znali jakékoli názvy sloupců kromě id
.
Funguje pouze pro "všechny sloupce cílové tabulky" . I když syntaxe funguje i pro hlavní podmnožinu, nadbytečné sloupce v cílové tabulce by byly resetovány na NULL pomocí DELETE
a INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) je potřeba, aby se předešlo problémům se souběžností/uzamykáním při souběžném zatížení zápisu, a to pouze proto, že neexistuje žádný obecný způsob, jak zamknout dosud neexistující řádky v Postgresu (uzamykání hodnot ).
Váš speciální požadavek má vliv pouze na UPDATE
část. Případné komplikace neplatí tam, kde existují řádky jsou ovlivněny. Ty jsou správně zamčené. Pro další zjednodušení můžete velikost písmen zmenšit na DELETE
a INSERT
:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES
(1, 'foo_upd', 'a') -- changed
, (2, 'bar', 'b') -- unchanged
, (3, 'baz', 'c') -- new
)
, del AS (
DELETE FROM tbl AS t
USING data d
WHERE t.id = d.id
-- AND t <> d -- optional, to avoid empty updates
) -- only works for complete rows
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO NOTHING
RETURNING t.id;
V modelu Postgres MVCC UPDATE
je z velké části stejný jako DELETE
a INSERT
každopádně (kromě některých rohových případů se souběžností, HOT aktualizací a velkých hodnot sloupců uložených mimo řádky). Protože přesto chcete nahradit všechny řádky, stačí odstranit konfliktní řádky před INSERT
. Odstraněné řádky zůstanou uzamčeny, dokud není transakce potvrzena. INSERT
může najít konfliktní řádky pro dříve neexistující hodnoty klíče pouze v případě, že je souběžná transakce vloží souběžně (za DELETE
, ale před INSERT
).
V tomto speciálním případě byste přišli o další hodnoty sloupců pro ovlivněné řádky. Nebyla vznesena žádná výjimka. Ale pokud mají konkurenční dotazy stejnou prioritu, není to problém:u některých vyhrál druhý dotaz řádky. Také, pokud je druhý dotaz podobný UPSERT, jeho alternativou je počkat na potvrzení této transakce a poté se ihned aktualizuje. „Vítězství“ by mohlo být Pyrrhovo vítězství.
O „prázdných aktualizacích“:
- Jak mohu (nebo mohu) SELECT DISTINCT na více sloupcích?
Ne, můj dotaz musí vyhrát!
Dobře, požádali jste o to:
WITH data(id) AS ( -- Only 1st column gets explicit name!
VALUES -- rest gets default names "column2", etc.
(1, 'foo_upd', NULL) -- changed
, (2, 'bar', NULL) -- unchanged
, (3, 'baz', NULL) -- new
, (4, 'baz', NULL) -- new
)
, ups AS (
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = t.id
WHERE false -- never executed, but locks the row!
RETURNING t.id
)
, del AS (
DELETE FROM tbl AS t
USING data d
LEFT JOIN ups u USING (id)
WHERE u.id IS NULL -- not inserted !
AND t.id = d.id
-- AND t <> d -- avoid empty updates - only for full rows
RETURNING t.id
)
, ins AS (
INSERT INTO tbl AS t
SELECT *
FROM data
JOIN del USING (id) -- conflict impossible!
RETURNING id
)
SELECT ARRAY(TABLE ups) AS inserted -- with UPSERT
, ARRAY(TABLE ins) AS updated -- with DELETE & INSERT;
Jak?
- První
data
CTE pouze poskytuje data. Místo toho může být stůl. - 2. CTE
ups
:UPSERT. Řádky s konfliktnímid
se nezmění, ale také zamknou . - 3. CTE
del
odstraní konfliktní řádky. Zůstanou uzamčeny. - Čtvrtý
ins
CTE vloží celé řádky . Povoleno pouze pro stejnou transakci - Poslední VÝBĚR slouží pouze k ukázce ukázky toho, co se stalo.
Pro kontrolu prázdných aktualizací test (před a po) pomocí:
SELECT ctid, * FROM tbl; -- did the ctid change?
Kontrola (komentována) na všechny změny v řádku AND t <> d
funguje i s hodnotami NULL, protože porovnáváme dvě napsané hodnoty řádku podle manuálu:
dvě hodnoty pole NULL jsou považovány za stejné a hodnota NULL je považována za větší než hodnota bez NULL
2. Dynamický SQL
Toto funguje i pro podmnožinu úvodních sloupců, přičemž se zachovají stávající hodnoty.
Trik je nechat Postgres dynamicky sestavit řetězec dotazu s názvy sloupců ze systémových katalogů a poté jej spustit.
Viz související odpovědi pro kód:
-
Aktualizujte více sloupců ve funkci spouštění v plpgsql
-
Hromadná aktualizace všech sloupců
-
SQL aktualizace polí jedné tabulky z polí jiné tabulky