Tento problém se týká ignorování duplicitní vložení pomocí PostgreSQL 9.2 nebo vyšší. Důvod, proč se ptám, je kvůli tomuto kódu:
-- Ignores duplicates.
INSERT INTO
db_table (tbl_column_1, tbl_column_2)
VALUES (
SELECT
unnseted_column,
param_association
FROM
unnest( param_array_ids ) AS unnested_column
);
Kód je nezatížen kontrolami existujících hodnot. (V této konkrétní situaci se uživatel nestará o chyby při vkládání duplikátů - vložení by mělo „fungovat“.) Přidání kódu v této situaci k výslovnému testování duplikátů způsobuje komplikace.
V PostgreSQL jsem našel několik způsobů, jak ignorovat duplicitní přílohy.
Vytvořte transakci, která zachytí jedinečná porušení omezení a nevykonáte žádnou akci:
BEGIN
INSERT INTO db_table (tbl_column) VALUES (v_tbl_column);
EXCEPTION WHEN unique_violation THEN
-- Ignore duplicate inserts.
END;
Vytvořte pravidlo pro ignorování duplikátů v dané tabulce:
CREATE OR REPLACE RULE db_table_ignore_duplicate_inserts AS
ON INSERT TO db_table
WHERE (EXISTS ( SELECT 1
FROM db_table
WHERE db_table.tbl_column = NEW.tbl_column)) DO INSTEAD NOTHING;
Moje otázky jsou většinou akademické:
Děkuju!
Jako odpovědi na další otázku (u které je tato považována za duplikát), existuje (od verze 9.5) nativní funkce UPSERT
. U starších verzí pokračujte ve čtení :)
Nastavil jsem test pro kontrolu možností. Přidám níže uvedený kód, který lze spustit v psql
na linux/unixovém boxu (jednoduše proto, že kvůli přehlednosti výsledků jsem výstupem instalačních příkazů poslal na /dev/null
- v krabici se systémem Windows je možné místo toho zvolit soubor protokolu).
Snažil jsem se, aby různé výsledky byly srovnatelné pomocí více než jednoho (tj. 100) INSERT
na typ, spuštění ze smyčky uvnitř uložené procedury plpgsql
. Před každým spuštěním je navíc tabulka resetována zkrácením a opětovným vložením původních dat.
Když zkontrolujete několik testovacích cyklů, vypadá to, že pomocí pravidla a explicitně přidáním příkazu WHERE NOT EXISTS
Ot INSERT
strávíte podobný čas, zatímco vyřízení výjimky zabere podstatně více času.
To není tak překvapující :
Tip: Blok obsahující klauzuli EXCEPTION je výrazně dražší než vstup bez bloku. Proto NEVYUŽÍVEJTE VÝNIMKU bez nutnosti.
Osobně, kvůli čitelnosti a udržovatelnosti, raději přidávám bit WHERE NOT EXISTS
Do samotných INSERT
. Stejně jako u triggerů (které lze také testovat zde) je ladění (nebo jednoduše sledování chování INSERT
) komplikovanější se současnými pravidly.
A kód, který jsem použil (neváhejte poukazovat na mylné představy nebo jiné problémy):
\o /dev/null
\timing off
-- set up data
DROP TABLE IF EXISTS insert_test;
CREATE TABLE insert_test_base_data (
id integer PRIMARY KEY,
col1 double precision,
col2 text
);
CREATE TABLE insert_test (
id integer PRIMARY KEY,
col1 double precision,
col2 text
);
INSERT INTO insert_test_base_data
SELECT i, (SELECT random() AS r WHERE s.i = s.i)
FROM
generate_series(2, 200, 2) s(i)
;
UPDATE insert_test_base_data
SET col2 = md5(col1::text)
;
INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;
-- function with exception block to be called later
CREATE OR REPLACE FUNCTION f_insert_test_insert(
id integer,
col1 double precision,
col2 text
)
RETURNS void AS
$body$
BEGIN
INSERT INTO insert_test
VALUES ($1, $2, $3)
;
EXCEPTION
WHEN unique_violation
THEN NULL;
END;
$body$
LANGUAGE plpgsql;
-- function running plain SQL ... WHERE NOT EXISTS ...
CREATE OR REPLACE FUNCTION insert_test_where_not_exists()
RETURNS void AS
$body$
BEGIN
FOR i IN 1 .. 100
LOOP
INSERT INTO insert_test
SELECT i, rnd, md5(rnd::text)
FROM (SELECT random() AS rnd) r
WHERE NOT EXISTS (
SELECT 1
FROM insert_test
WHERE id = i
)
;
END LOOP;
END;
$body$
LANGUAGE plpgsql;
-- call a function with exception block
CREATE OR REPLACE FUNCTION insert_test_function_with_exception_block()
RETURNS void AS
$body$
BEGIN
FOR i IN 1 .. 100
LOOP
PERFORM f_insert_test_insert(i, rnd, md5(rnd::text))
FROM (SELECT random() AS rnd) r
;
END LOOP;
END;
$body$
LANGUAGE plpgsql;
-- leave checking existence to a rule
CREATE OR REPLACE FUNCTION insert_test_rule()
RETURNS void AS
$body$
BEGIN
FOR i IN 1 .. 100
LOOP
INSERT INTO insert_test
SELECT i, rnd, md5(rnd::text)
FROM (SELECT random() AS rnd) r
;
END LOOP;
END;
$body$
LANGUAGE plpgsql;
\o
\timing on
\echo
\echo 'check before INSERT'
SELECT insert_test_where_not_exists();
\echo
\o /dev/null
\timing off
TRUNCATE insert_test;
INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;
\timing on
\o
\echo 'catch unique-violation'
SELECT insert_test_function_with_exception_block();
\echo
\echo 'implementing a RULE'
\o /dev/null
\timing off
TRUNCATE insert_test;
INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;
CREATE OR REPLACE RULE db_table_ignore_duplicate_inserts AS
ON INSERT TO insert_test
WHERE EXISTS (
SELECT 1
FROM insert_test
WHERE id = NEW.id
)
DO INSTEAD NOTHING;
\o
\timing on
SELECT insert_test_rule();