2010-10-11 27 views
8

Estoy convirtiendo una tabla MySQL en PostgreSQL por primera vez en mi vida y encontrándome con el problema tradicional de novato de no tener autoincremento.Inserciones manuales en una tabla de postgres con una secuencia de clave primaria

Ahora descubrí que la solución postgres es usar una secuencia y luego solicitar nextval() de esta secuencia como el valor predeterminado cada vez que inserta. También he leído que el tipo SERIAL crea automáticamente una secuencia y una clave principal, y que nextval() incrementa el contador incluso cuando se lo llama dentro de las transacciones para evitar bloquear la secuencia.

Lo que no encuentro direccionado es la cuestión de qué ocurre cuando inserta manualmente valores en un campo con una restricción ÚNICA o PRIMARIA y una próximaval() de una secuencia como predeterminada. Hasta donde puedo ver, esto hace que INSERT falle cuando la secuencia alcanza ese valor.

¿Hay una manera simple (o común) de arreglar esto?

Una explicación clara sería muy apreciada.

Actualización: Si usted siente que no debería hacer esto, nunca será capaz de solucionar esto o estoy haciendo algunas suposiciones incorrectas, no dude en señalar hacia fuera en sus respuestas. Por encima de todo, por favor, dime que hacer en lugar de ofrecer a los programadores una base de datos estable y robusto que no puede ser dañado con una inserción sencilla (preferiblemente sin ocultar todo atrás procedimientos almacenados)

Respuesta

8

Si va a migrar los datos a continuación Quitaría la restricción de secuencia en la columna, realizaría todas sus inserciones, use setval() para establecer la secuencia al valor máximo de sus datos y luego restablezca la secuencia de columnas nextval() por defecto.

+1

Pero eso es solo evitar el problema :) Quiero una base de datos que no falle incluso si alguien hace una inserción con una ID fija donde la predeterminada es una generada. –

+0

Si la ID fija ya existe, es probable que tenga problemas. Lo mejor es no insertar manualmente todo lo que diría. Podría escribir un desencadenador de inserción para llamar a una función que verifica los valores de secuencia y el ID insertado, pero no lo recomendaría. –

+0

¿Por qué no? ¿Cuál es el inconveniente? –

2

No entiendo exactamente su pregunta, pero si su objetivo es simplemente insertar, y tener un campo válido (por ejemplo, una identificación), entonces inserte los valores sin el campo id, eso es lo que significa "por defecto" para. Funcionará.

E.g. habiendo un id serial NOT NULL y un CONSTRAINT table_pkey PRIMARY KEY(id) en la definición de la tabla, se establecerá automáticamente la identificación y se incrementará automáticamente la secuencia table_id_seq.

+0

Gracias, pero quiero saber cómo evitar un error si crea una nueva tabla, inserta manualmente un campo con id 4 y luego inserta campos sin id por un tiempo. –

3

Puede crear un disparador que comprobará si currval('id_sequence_name')>=NEW.id.

Si su transacción no usó el valor predeterminado o nextval('id_sequence_name'), entonces una función currval arrojará un error, ya que solo funciona cuando la secuencia se actualizó en la sesión actual. Si usa nextval y luego intenta insertar una clave principal más grande, arrojará otro error. Una transacción será abortada.

Esto evitaría la inserción de cualquier clave primaria incorrecta que se rompa en serie.

código Ejemplo:

create table test (id serial primary key, value text); 

create or replace function test_id_check() returns trigger language plpgsql as 
$$ begin 
    if (currval('test_id_seq')<NEW.id) then 
    raise exception 'currval(test_id_seq)<id'; 
    end if; 
    return NEW; 
end; $$; 

create trigger test_id_seq_check before insert or update of id on test 
    for each row execute procedure test_id_check(); 

A continuación, insertar la clave principal defecto funciona bien:

insert into test(value) values ('a'),('b'),('c'),('d'); 

Pero la inserción de clave principal demasiado grande será el error y abortará:

insert into test(id, value) values (10,'z'); 
4

Para ampliar la gran respuesta de Tometzky, aquí hay una versión más general:

CREATE OR REPLACE FUNCTION check_serial() RETURNS trigger AS $$ 
BEGIN 
    IF currval(TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME || '_' || TG_ARGV[0] || '_seq') < 
    (row_to_json(NEW)->>TG_ARGV[0])::bigint 
    THEN RAISE SQLSTATE '55000'; -- same as currval() of uninitialized sequence 
    END IF; 
    RETURN NULL; 
EXCEPTION  
    WHEN SQLSTATE '55000' 
    THEN RAISE 'manual entry of serial field %.%.% disallowed', 
    TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_ARGV[0] 
    USING HINT = 'use DEFAULT instead of specifying value manually', 
     SCHEMA = TG_TABLE_SCHEMA, TABLE = TG_TABLE_NAME, COLUMN = TG_ARGV[0]; 
END; 
$$ LANGUAGE plpgsql; 

que se pueden aplicar a cualquier columna, digamos test.id, de esta manera:

CREATE CONSTRAINT TRIGGER test_id_check 
    AFTER INSERT OR UPDATE OF id ON test 
    FOR EACH ROW EXECUTE PROCEDURE check_serial(id); 
0

¿Qué pasa con el uso de un cheque?

CREATE SEQUENCE pk_test 
    INCREMENT 1 
    MINVALUE 1 
    MAXVALUE 9223372036854775807 
    START 1 
    CACHE 1; 

CREATE TABLE test (
    id INT PRIMARY KEY CHECK (id=currval('pk_test')) DEFAULT nextval('pk_test'), 
    num int not null 
    ); 
ALTER SEQUENCE pk_test OWNED BY test.id; 

-- Testing: 
INSERT INTO test (num) VALUES (3) RETURNING id, num; 
1,3 -- OK 
2,3 -- OK 

INSERT INTO test (id, num) values (30,3) RETURNING id, num; 
/* 
ERROR: new row for relation "test" violates check constraint "test_id_check" 
DETAIL: Failing row contains (30, 3). 

********** Error ********** 

ERROR: new row for relation "test" violates check constraint "test_id_check" 
SQL state: 23514 
Detail: Failing row contains (30, 3). 
*/ 

DROP TABLE test; 
Cuestiones relacionadas