Aggiunta di "seriale" alla colonna esistente in Postgres


91

Ho una piccola tabella (~ 30 righe) nel mio database Postgres 9.0 con un campo ID intero (la chiave primaria) che attualmente contiene numeri interi sequenziali univoci a partire da 1, ma che non è stato creato utilizzando la parola chiave "seriale".

Come posso modificare questa tabella in modo tale che d'ora in poi gli inserimenti in questa tabella facciano sì che questo campo si comporti come se fosse stato creato con "seriale" come tipo?


5
Cordiali saluti, lo SERIALpseudo-tipo è ora legacy , soppiantato dalla nuova GENERATED … AS IDENTITYfunzionalità definita in SQL: 2003 , in Postgres 10 e versioni successive. Vedi spiegazione .
Basil Bourque

Per la versione moderna Postgres (> = 10) Vedere questa domanda: stackoverflow.com/questions/2944499
a_horse_with_no_name

Risposte:


132

Guarda i seguenti comandi (specialmente il blocco commentato).

DROP TABLE foo;
DROP TABLE bar;

CREATE TABLE foo (a int, b text);
CREATE TABLE bar (a serial, b text);

INSERT INTO foo (a, b) SELECT i, 'foo ' || i::text FROM generate_series(1, 5) i;
INSERT INTO bar (b) SELECT 'bar ' || i::text FROM generate_series(1, 5) i;

-- blocks of commands to turn foo into bar
CREATE SEQUENCE foo_a_seq;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');
ALTER TABLE foo ALTER COLUMN a SET NOT NULL;
ALTER SEQUENCE foo_a_seq OWNED BY foo.a;    -- 8.2 or later

SELECT MAX(a) FROM foo;
SELECT setval('foo_a_seq', 5);  -- replace 5 by SELECT MAX result

INSERT INTO foo (b) VALUES('teste');
INSERT INTO bar (b) VALUES('teste');

SELECT * FROM foo;
SELECT * FROM bar;

Dato che stai menzionando le chiavi primarie nel tuo OP, potresti anche volerlo ALTER TABLE foo ADD PRIMARY KEY (a).
Skippy le Grand Gourou

SERIAL è zucchero sintattico e non è memorizzato nei metadati DB, quindi il codice sopra sarebbe equivalente al 100%.
DKroot

Se esiste la possibilità che la tabella di destinazione sia stata creata da un utente diverso, devi ALTER TABLE foo OWNER TO current_user;prima farlo .
DKroot

2
Non dovresti essere impostato MAX(a)+1in setval? SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro

48

Puoi anche usare START WITHper iniziare una sequenza da un punto particolare, sebbene setval compia la stessa cosa, come nella risposta di Eulero, ad esempio,

SELECT MAX(a) + 1 FROM foo;
CREATE SEQUENCE foo_a_seq START WITH 12345; -- replace 12345 with max above
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq');

28

TL; DR

Ecco una versione in cui non è necessario che un umano legga un valore e lo digiti da solo.

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

Un'altra opzione sarebbe quella di utilizzare il riutilizzabile Functioncondiviso alla fine di questa risposta.


Una soluzione non interattiva

Aggiungendo solo alle altre due risposte, per quelli di noi che hanno bisogno di Sequencecrearli con uno script non interattivo , mentre ad esempio patchano un DB live.

Cioè, quando non vuoi SELECTil valore manualmente e digitalo tu stesso in CREATEun'istruzione successiva .

In breve, si può non fare:

CREATE SEQUENCE foo_a_seq
    START WITH ( SELECT max(a) + 1 FROM foo );

... poiché la START [WITH]clausola in si CREATE SEQUENCEaspetta un valore , non una sottoquery.

Nota: Come regola generale, che si applica a tutti i non-CRUD ( vale a dire : qualcosa di diverso INSERT, SELECT, UPDATE, DELETE) le dichiarazioni con pgSQL per quanto ne so.

Tuttavia, setval()fa! Quindi, quanto segue va assolutamente bene:

SELECT setval('foo_a_seq', max(a)) FROM foo;

Se non ci sono dati e non (vuoi) saperlo, usa coalesce()per impostare il valore predefinito:

SELECT setval('foo_a_seq', coalesce(max(a), 0)) FROM foo;
--                         ^      ^         ^
--                       defaults to:       0

Tuttavia, avere il valore della sequenza corrente impostato su 0è maldestro, se non illegale.
L'utilizzo della forma a tre parametri di setvalsarebbe più appropriato:

--                                             vvv
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
--                                                  ^   ^
--                                                is_called

L'impostazione del terzo parametro opzionale di setvala falseimpedirà al successivo nextvaldi avanzare nella sequenza prima di restituire un valore, e quindi:

il successivo nextvalrestituirà esattamente il valore specificato e l'avanzamento della sequenza inizia con quanto segue nextval.

- da questa voce nella documentazione

In una nota non correlata, puoi anche specificare la colonna che possiede Sequencedirettamente con CREATE, non devi modificarla in seguito:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

In sintesi:

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;
SELECT setval('foo_a_seq', coalesce(max(a), 0) + 1, false) FROM foo;
ALTER TABLE foo ALTER COLUMN a SET DEFAULT nextval('foo_a_seq'); 

Usare un Function

In alternativa, se hai intenzione di farlo per più colonne, potresti optare per l'utilizzo di un file Function.

CREATE OR REPLACE FUNCTION make_into_serial(table_name TEXT, column_name TEXT) RETURNS INTEGER AS $$
DECLARE
    start_with INTEGER;
    sequence_name TEXT;
BEGIN
    sequence_name := table_name || '_' || column_name || '_seq';
    EXECUTE 'SELECT coalesce(max(' || column_name || '), 0) + 1 FROM ' || table_name
            INTO start_with;
    EXECUTE 'CREATE SEQUENCE ' || sequence_name ||
            ' START WITH ' || start_with ||
            ' OWNED BY ' || table_name || '.' || column_name;
    EXECUTE 'ALTER TABLE ' || table_name || ' ALTER COLUMN ' || column_name ||
            ' SET DEFAULT nextVal(''' || sequence_name || ''')';
    RETURN start_with;
END;
$$ LANGUAGE plpgsql VOLATILE;

Usalo così:

INSERT INTO foo (data) VALUES ('asdf');
-- ERROR: null value in column "a" violates not-null constraint

SELECT make_into_serial('foo', 'a');
INSERT INTO foo (data) VALUES ('asdf');
-- OK: 1 row(s) affected

Ottima risposta, ma tieni presente coalesce(max(a), 0))che non funzionerà la maggior parte del tempo, poiché gli ID di solito iniziano da 1. Il modo più corretto sarebbecoalesce(max(a), 1))
Amiko

1
Grazie @Amiko per il commento! La setvalfunzione in realtà imposta solo l '"ultimo valore utilizzato" corrente per la sequenza. Il prossimo valore disponibile (il primo ad essere effettivamente utilizzato) sarà uno in più! L'utilizzo setval(..., coalesce(max(a), 1))su una colonna vuota lo imposterebbe per "iniziare" con 2(il prossimo valore disponibile), come illustrato nella documentazione .
ccjmne

1
@Amiko Hai ragione nel dire che c'è un problema nel mio codice, però: currvalnon dovrebbe mai esserlo 0, anche se non si rifletterebbe nel dataset effettivo. Utilizzando il modulo di tre parametri di setvalsarebbe più appropriato: setval(..., coalesce(max(a), 0) + 1, false). Risposta aggiornata di conseguenza!
ccjmne

1
D'accordo, me lo sono perso totalmente. Grazie per la risposta mi ha fatto risparmiare tempo.
Amiko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.