Postgresの既存の列に「シリアル」を追加する


91

Postgres 9.0データベースに小さなテーブル(〜30行)があり、整数IDフィールド(主キー)が現在1から始まる一意の連続した整数が含まれていますが、 'serial'キーワードを使用して作成されていません。

今後、このテーブルに挿入すると、このフィールドがタイプとして「シリアル」を使用して作成されたかのように動作するように、このテーブルを変更するにはどうすればよいですか?


5
参考までに、Postgres 10以降では、SQL:2003定義された新しい機能に代わって、SERIAL疑似型がレガシーになりました。説明を参照してください。GENERATED … AS IDENTITY
バジルブルク

最新のPostgresバージョン(> = 10)については、次の質問を参照してください:stackoverflow.com/questions/2944499
a_horse_with_no_name

回答:


132

次のコマンド(特にコメントブロック)を確認してください。

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;

OPで主キーについて言及しているので、あなたもそうしたいかもしれませんALTER TABLE foo ADD PRIMARY KEY (a)
Skippy le Grand Gourou 2014

SERIALは構文糖であり、DBメタデータに格納されないため、上記のコードは100%等価です。
DKroot

ターゲットテーブルが別のユーザーによって作成された可能性がある場合は、ALTER TABLE foo OWNER TO current_user;最初に行う必要があります。
DKroot

2
MAX(a)+1setvalを設定するべきではありませんか?SELECT MAX(a)+1 FROM foo; SELECT setval('foo_a_seq', 6);
SunnyPro 2018

48

を使用START WITHして、特定のポイントからシーケンスを開始することもできますが、setvalはオイラーの回答と同じように実行されます。たとえば、

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

これは、値を読み取って自分で入力するのに人間を必要としないバージョンです。

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

別のオプションはFunction、この回答の終わりに再利用可能な共有を採用することです。


非インタラクティブなソリューション

他の2つの答えに追加するだけで、たとえばSequence非対話型スクリプトによってこれらのを作成し、ライブ風のDBにパッチを適用する必要がある場合などです。

つまりSELECT、値を手動で入力したくない場合、次のCREATEステートメントに自分で入力します。

つまり、次のことできません。

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

...のSTART [WITH]句は、サブクエリではなく、CREATE SEQUENCE期待するためです

注:(すべての非CRUDに適用される経験則として、すなわち:以外INSERTSELECTUPDATEDELETE内の文)pgSQLに私の知る限り。

しかし、setval()そうです!したがって、以下は絶対に問題ありません。

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

データがなく、それを知らない(知りたい)場合は、を使用coalesce()してデフォルト値を設定します。

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

ただし、現在のシーケンス値をに設定すること0は、違法ではないにしても不格好です。
3パラメータ形式のを使用setvalするのがより適切です。

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

オプションの3番目のパラメーターをsetvaltoに設定するfalseと、next nextvalが値を返す前にシーケンスを進めることができなくなり、次のようになります。

nextvalは正確に指定された値を返し、シーケンスの前進は以下から始まりますnextval

ドキュメントのこのエントリから

関連のないメモでは、をSequence直接所有する列を指定することもできます。CREATE後で変更する必要はありません。

CREATE SEQUENCE foo_a_seq OWNED BY foo.a;

要約すれば:

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

を使って Function

または、複数の列に対してこれを行う予定の場合は、実際のを使用することを選択できます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;

次のように使用します。

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

正解です。ただしcoalesce(max(a), 0))、Idsは通常1から始まるため、ほとんどの場合は機能しません。より正確な方法はcoalesce(max(a), 1))
Amiko

1
コメントをありがとう@Amiko!setvalこの関数は実際にはシーケンスの現在の「最新使用される値」を設定します。次に使用可能な値(最初に実際に使用される値)がもう1つあります!ドキュメントに示されいるようsetval(..., coalesce(max(a), 1))に、空の列でを使用すると、2(次に使用可能な値)で「開始」するように設定されます
ccjmne

1
@Amikoあなたは正しいものの、私のコードに問題があると言っている:currvalしてはいけません0、それは実際のデータセットには反映されない場合でも、。の3つのパラメータ形式を使用setvalするのがより適切ですsetval(..., coalesce(max(a), 0) + 1, false)。それに応じて更新された回答!
ccjmne

1
同意した、私はそれを完全に逃した。答えてくれてありがとう。
Amiko
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.