2010-08-12 53 views
10

Estoy actualizando una base de datos Postgres 8.4 (desde el código C#) y la tarea básica es bastante simple: ACTUALIZAR una fila existente o INSERTAR una nueva si no lo hace t existir todavía Normalmente, me gustaría hacer esto:Postgres UPSERT (INSERT o UPDATE) solo si el valor es diferente

UPDATE my_table 
SET value1 = :newvalue1, ..., updated_time = now(), updated_username = 'evgeny' 
WHERE criteria1 = :criteria1 AND criteria2 = :criteria2 

y si 0 filas afectadas luego hacer un INSERT:

INSERT INTO my_table(criteria1, criteria2, value1, ...) 
VALUES (:criteria1, :criteria2, :newvalue1, ...) 

Hay un pequeño giro, sin embargo. No deseo cambiar las columnas updated_time y updated_username a menos que alguno de los valores nuevos sea realmente diferente de los valores existentes para evitar confundir a los usuarios acerca de cuándo se actualizaron los datos.

Si solo estuviera haciendo una ACTUALIZACIÓN, entonces podría agregar DONDE las condiciones para los valores también, pero eso no funcionará aquí, porque si la base de datos ya está actualizada, la actualización afectará a 0 filas y luego lo haría intente INSERTAR.

¿Alguien puede pensar en una forma elegante de hacer esto, que no sea SELECCIONAR, y luego ACTUALIZAR o INSERTAR?

+0

posible duplicado de [Insertar, en la actualización duplicada (postgresql)] (http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql) –

+0

No, no es un duplicado. La respuesta allí simplemente encapsula en una función lo que he escrito arriba. – EMP

Respuesta

-1

Comenzar una transacción. Use una selección para ver si los datos que estaría insertando ya existen, si lo hace, no haga nada, de lo contrario actualice, si no existe, luego inserte. Finalmente cierre la transacción.

+2

Esto no es óptimo porque un caso de uso común podría ser insertar a granel un montón de filas en una transacción –

4

Echa un vistazo a un desencadenador UPDATE antes para comprobar y establecer los valores correctos:

CREATE OR REPLACE FUNCTION my_trigger() RETURNS TRIGGER LANGUAGE plpgsql AS 
$$ 
BEGIN 
    IF OLD.content = NEW.content THEN 
     NEW.updated_time= OLD.updated_time; -- use the old value, not a new one. 
    ELSE 
     NEW.updated_time= NOW(); 
    END IF; 
    RETURN NEW; 
END; 
$$; 

Ahora usted ni siquiera tiene que mencionar el updated_time campo en su consulta UPDATE, que estará a cargo de la desencadenar.

http://www.postgresql.org/docs/current/interactive/plpgsql-trigger.html

5

dos cosas aquí. En primer lugar, dependiendo de los niveles de actividad en su base de datos, puede alcanzar una condición de carrera entre buscar un registro e insertarlo, mientras que otro proceso puede crear ese registro en el ínterin. El manual contiene un ejemplo de cómo hacer esto link example

Para evitar hacer una actualización no es el procedimiento suppress_redundant_updates_trigger(). Para utilizar esto como desearía, debe tener dos activadores de actualización antes de que el primero llame a suppress_redundant_updates_trigger() para cancelar la actualización si no se realizó ningún cambio y el segundo para establecer la marca de tiempo y el nombre de usuario si se realiza la actualización. Los disparadores se disparan en orden alfabético. Hacer esto también significaría cambiar el código en el ejemplo anterior para probar la inserción antes de la actualización.

Ejemplo de cómo funciona la actualización de supresión:

DROP TABLE sru_test; 

    CREATE TABLE sru_test(id integer not null primary key, 
    data text, 
    updated timestamp(3)); 

    CREATE TRIGGER z_min_update 
    BEFORE UPDATE ON sru_test 
    FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); 

    DROP FUNCTION set_updated(); 

    CREATE FUNCTION set_updated() 
    RETURNS TRIGGER 
    AS $$ 
    DECLARE 
    BEGIN 
     NEW.updated := now(); 
     RETURN NEW; 
    END; 
    $$ LANGUAGE plpgsql; 

    CREATE TRIGGER zz_set_updated 
    BEFORE INSERT OR UPDATE ON sru_test 
    FOR EACH ROW EXECUTE PROCEDURE set_updated(); 

insert into sru_test(id,data) VALUES (1,'Data 1'); 
insert into sru_test(id,data) VALUES (2,'Data 2'); 

select * from sru_test; 

update sru_test set data = 'NEW'; 

select * from sru_test; 

update sru_test set data = 'NEW'; 

select * from sru_test; 

update sru_test set data = 'ALTERED' where id = 1; 

select * from sru_test; 

update sru_test set data = 'NEW' where id = 2; 

select * from sru_test; 
0

La cláusula RETURNING le permite encadenar sus consultas; la segunda consulta usa los resultados de la primera. (en este caso para evitar volver a tocar las mismas filas) (DEVOLVER está disponible desde postgres 8.4)

Se muestra aquí incrustado en una función, pero funciona para SQL normal, también

DROP SCHEMA tmp CASCADE; 
CREATE SCHEMA tmp ; 
SET search_path=tmp; 

CREATE TABLE my_table 
     (updated_time timestamp NOT NULL DEFAULT now() 
     , updated_username varchar DEFAULT '_none_' 
     , criteria1 varchar NOT NULL 
     , criteria2 varchar NOT NULL 
     , value1 varchar 
     , value2 varchar 
     , PRIMARY KEY (criteria1,criteria2) 
     ); 

INSERT INTO my_table (criteria1,criteria2,value1,value2) 
SELECT 'C1_' || gs::text 
     , 'C2_' || gs::text 
     , 'V1_' || gs::text 
     , 'V2_' || gs::text 
FROM generate_series(1,10) gs 
     ; 

SELECT * FROM my_table ; 

CREATE function funky(_criteria1 text,_criteria2 text, _newvalue1 text, _newvalue2 text) 
RETURNS VOID 
AS $funk$ 
WITH ins AS (
     INSERT INTO my_table(criteria1, criteria2, value1, value2, updated_username) 
     SELECT $1, $2, $3, $4, COALESCE(current_user, 'evgeny') 
     WHERE NOT EXISTS (
       SELECT * FROM my_table nx 
       WHERE nx.criteria1 = $1 AND nx.criteria2 = $2 
       ) 
     RETURNING criteria1 AS criteria1, criteria2 AS criteria2 
     ) 
     UPDATE my_table upd 
     SET value1 = $3, value2 = $4 
     , updated_time = now() 
     , updated_username = COALESCE(current_user, 'evgeny') 
     WHERE 1=1 
     AND criteria1 = $1 AND criteria2 = $2 -- key-condition 
     AND (value1 <> $3 OR value2 <> $4) -- row must have changed 
     AND NOT EXISTS (
       SELECT * FROM ins -- the result from the INSERT 
       WHERE ins.criteria1 = upd.criteria1 
       AND ins.criteria2 = upd.criteria2 
       ) 
     ; 
$funk$ language sql 
     ; 

SELECT funky('AA', 'BB' , 'CC', 'DD');   -- INSERT 
SELECT funky('C1_3', 'C2_3' , 'V1_3', 'V2_3'); -- (null) UPDATE 
SELECT funky('C1_7', 'C2_7' , 'V1_7', 'V2_7777'); -- (real) UPDATE 

SELECT * FROM my_table ; 

RESULTADO:

 updated_time  | updated_username | criteria1 | criteria2 | value1 | value2 
----------------------------+------------------+-----------+-----------+--------+--------- 
2013-03-13 16:37:55.405267 | _none_   | C1_1  | C2_1  | V1_1 | V2_1 
2013-03-13 16:37:55.405267 | _none_   | C1_2  | C2_2  | V1_2 | V2_2 
2013-03-13 16:37:55.405267 | _none_   | C1_3  | C2_3  | V1_3 | V2_3 
2013-03-13 16:37:55.405267 | _none_   | C1_4  | C2_4  | V1_4 | V2_4 
2013-03-13 16:37:55.405267 | _none_   | C1_5  | C2_5  | V1_5 | V2_5 
2013-03-13 16:37:55.405267 | _none_   | C1_6  | C2_6  | V1_6 | V2_6 
2013-03-13 16:37:55.405267 | _none_   | C1_8  | C2_8  | V1_8 | V2_8 
2013-03-13 16:37:55.405267 | _none_   | C1_9  | C2_9  | V1_9 | V2_9 
2013-03-13 16:37:55.405267 | _none_   | C1_10  | C2_10  | V1_10 | V2_10 
2013-03-13 16:37:55.463651 | postgres   | AA  | BB  | CC  | DD 
2013-03-13 16:37:55.472783 | postgres   | C1_7  | C2_7  | V1_7 | V2_7777 
(11 rows) 
2

Postgres está recibiendo UPSERT apoyo. Actualmente se encuentra en el tree desde mayo 8 de 2015 (commit):

Esta característica se refiere a menudo como upsert.

Esto se implementa utilizando una nueva infraestructura llamada "inserción especulativa ". Es una variante optimista de inserción regular que primero realiza una comprobación previa para las tuplas existentes y luego intenta insertar . Si una tupla infractora se insertó simultáneamente, la tupla insertada especulativamente se elimina y se realiza un nuevo intento. Si la comprobación previa encuentra una tupla coincidente, se toma la acción ACTUALIZAR DO NOTHING o DO alternativa. Si la inserción tiene éxito sin detectar un conflicto , la tupla se considera insertada.

Una instantánea es available for download. Todavía no ha realizado a release.

Cuestiones relacionadas