it-swarm.com.de

Optimale Möglichkeit, doppelte Einfügungen zu ignorieren?

Hintergrund

Dieses Problem betrifft das Ignorieren doppelter Einfügungen mit PostgreSQL 9.2 oder höher. Der Grund, den ich frage, ist wegen dieses Codes:

  -- 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
  );

Der Code wird nicht durch Überprüfungen auf vorhandene Werte belastet. (In dieser speziellen Situation kümmert sich der Benutzer nicht um Fehler beim Einfügen von Duplikaten - das Einfügen sollte "nur funktionieren".) Das Hinzufügen von Code in dieser Situation zum expliziten Testen auf Duplikate führt zu Komplikationen.

Problem

In PostgreSQL habe ich einige Möglichkeiten gefunden, doppelte Einfügungen zu ignorieren.

Duplikate Nr. 1 ignorieren

Erstellen Sie eine Transaktion, die eindeutige Verstöße gegen Einschränkungen abfängt, ohne Maßnahmen zu ergreifen:

  BEGIN
    INSERT INTO db_table (tbl_column) VALUES (v_tbl_column);
  EXCEPTION WHEN unique_violation THEN
    -- Ignore duplicate inserts.
  END;

Duplikate Nr. 2 ignorieren

Erstellen Sie eine Regel, um Duplikate in einer bestimmten Tabelle zu ignorieren:

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;

Fragen

Meine Fragen sind meist akademisch:

  • Welche Methode ist am effizientesten?
  • Welche Methode ist am wartbarsten und warum?
  • Was ist die Standardmethode, um Fehler beim Einfügen von Duplikaten mit PostgreSQL zu ignorieren?
  • Gibt es eine technisch effizientere Möglichkeit, doppelte Einfügungen zu ignorieren? wenn ja, was ist das?

Vielen Dank!

33
Dave Jarvis

Wie die Antworten auf die andere Frage (von der diese als Duplikat betrachtet wird) erwähnen, gibt es (seit Version 9.5) eine native UPSERT Funktionalität. Für ältere Versionen lesen Sie weiter :)

Ich habe einen Test zur Überprüfung der Optionen eingerichtet. Ich werde den folgenden Code einfügen, der in psql auf einer Linux/Unix-Box ausgeführt werden kann (einfach, weil ich aus Gründen der Klarheit der Ergebnisse die Ausgabe der Setup-Befehle an /dev/null - auf einer Windows-Box könnte man stattdessen eine Protokolldatei auswählen).

Ich habe versucht, verschiedene Ergebnisse vergleichbar zu machen, indem ich mehr als eine (dh 100) INSERT pro Typ verwendet habe, die aus einer Schleife in einer gespeicherten Prozedur plpgsql ausgeführt wurde. Zusätzlich wird die Tabelle vor jedem Lauf zurückgesetzt, indem die Originaldaten abgeschnitten und erneut eingefügt werden.

Wenn Sie einige Testläufe überprüfen, sieht es so aus, als würde die Verwendung der Regel und das explizite Hinzufügen von WHERE NOT EXISTS Der Anweisung INSERT eine ähnliche Zeit in Anspruch nehmen, während das Ausführen einer Ausnahme erheblich mehr Zeit in Anspruch nimmt.

Letzteres ist nicht das überraschend :

Tipp: Ein Block mit einer EXCEPTION-Klausel ist beim Betreten und Beenden erheblich teurer als ein Block ohne eine. Verwenden Sie daher EXCEPTION nicht ohne Notwendigkeit.

Persönlich ziehe ich es aus Gründen der Lesbarkeit und Wartbarkeit vor, das Bit WHERE NOT EXISTS Zu den INSERTs selbst hinzuzufügen. Genau wie bei Triggern (die auch hier getestet werden könnten) ist das Debuggen (oder einfach das Verfolgen des Verhaltens von INSERT) mit den vorhandenen Regeln komplizierter.

Und der Code, den ich verwendet habe (zögern Sie nicht, auf falsche Vorstellungen oder andere Probleme hinzuweisen):

\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