it-swarm-eu.dev

Optimální způsob, jak ignorovat duplicitní přílohy?

Pozadí

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.

Problém

V PostgreSQL jsem našel několik způsobů, jak ignorovat duplicitní přílohy.

Ignorovat duplikáty # 1

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;

Ignorovat duplikáty # 2

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;

Otázky

Moje otázky jsou většinou akademické:

  • Jaká metoda je nejúčinnější?
  • Jaká metoda je nejvíce udržovatelná a proč?
  • Jaký je standardní způsob, jak ignorovat chyby duplikace vložení s PostgreSQL?
  • Existuje technicky účinnější způsob, jak ignorovat duplicitní vložky; pokud ano, co to je?

Děkuju!

33
Dave Jarvis

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();
22
dezso