web-dev-qa-db-ja.com

PostgreSQLでのシーケンスの圧縮

id serial PRIMARY KEY PostgreSQLテーブルの列。対応する行を削除したため、多くのidsが欠落しています。

次に、シーケンスを再起動し、元のidの順序が維持されるようにidsを再割り当てすることで、テーブルを「圧縮」したいと思います。出来ますか?

例:

  • 今:

 id | data  
----+-------
  1 | hello
  2 | world
  4 | foo
  5 | bar
  • 後:

 id | data  
----+-------
  1 | hello
  2 | world
  3 | foo
  4 | bar

StackOverflowの回答 で提案されたものを試しましたが、うまくいきませんでした:

# alter sequence t_id_seq restart;
ALTER SEQUENCE
# update t set id=default;
ERROR:  duplicate key value violates unique constraint t_pkey
DETAIL:  Key (id)=(1) already exists.
9
rubik

まず、シーケンスのギャップが予想されます。それらを本当に削除する必要があるかどうか自問してください。あなたがそれと一緒に暮らすなら、あなたの人生はより単純になります。ギャップのない数値を取得するには、(多くの場合)VIEWrow_number()と一緒に使用します。この関連する回答の例:

ここにギャップを取り除くためのいくつかのレシピがあります。

1.新しい、元のテーブル

一意の違反とテーブルの膨張による複雑化を回避し、高速ですの単純な場合のみFK参照、テーブルのビュー、またはその他のビューに拘束されないオブジェクト、または同時アクセス。事故を避けるためにoneトランザクションでそれを行います:

_BEGIN;
LOCK tbl;

CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL);

INSERT INTO tbl_new -- no target list in this case
SELECT row_number() OVER (ORDER BY id), data  -- all columns in default order
FROM   tbl;

ALTER SEQUENCE tbl_id_seq OWNED BY tbl_new.id;  -- make new table own sequence

DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

COMMIT;
_

CREATE TABLE tbl_new (LIKE tbl INCLUDING ALL) 構造体を含む構造をコピーします。元のテーブルの制約とデフォルト。次に、新しいテーブル列にシーケンスを所有させます。

そして、それを新しい最大値にリセットします。

これには、新しいテーブルが膨張せず、idでクラスター化されるという利点があります。

2. UPDATEが適切に配置されている

これにより多くのデッド行が生成され、後で(auto-)VACUUMが必要になります。

serial column_PRIMARY KEY_(あなたの場合のように)であるか、 UNIQUE制約では、プロセス内で一意の違反を回避する必要があります。 PK/UNIQUE制約の(安価な)デフォルトは_NOT DEFERRABLE_で、すべての単一行の後にチェックを強制します。 SOに関するこの関連質問の下のすべての詳細:

制約をDEFERRABLEとして定義することができます(これにより、コストが高くなります)。
Or制約を削除し、完了したら再び追加できます。

_BEGIN;

LOCK tbl;

ALTER TABLE tbl DROP CONSTRAINT tbl_pkey;  -- remove PK

UPDATE tbl t  -- intermediate unique violations are ignored now
SET    id = t1.new_id
FROM  (SELECT id, row_number() OVER (ORDER BY id) AS new_id FROM tbl) t1
WHERE  t.id = t1.id;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

ALTER TABLE tbl ADD CONSTRAINT tbl_pkey PRIMARY KEY(id); -- add PK back

COMMIT;
_

どちらも-- _FOREIGN KEY_ 制約を参照している間は、どちらもは使用できません( ドキュメントごと )のため、列:

参照される列は、参照されるテーブル内の遅延できない一意または主キー制約の列である必要があります。

(関連するすべてのテーブルをロックして)FK制約を削除/再作成し、すべてのFK値を手動で更新する必要があります(オプション3。を参照)。または、競合を回避するために、2番目のUPDATEを使用して値を移動する必要があります。たとえば、負の数がないと仮定します。

_BEGIN;
LOCK tbl;

UPDATE tbl SET id = id * -1;  -- avoid conflicts

UPDATE tbl t
SET    id = t1.new_id
FROM  (SELECT id, row_number() OVER (ORDER BY id DESC) AS new_id FROM tbl) t1
WHERE  t.id = t1.id;

SELECT setval('tbl_id_seq', max(id)) FROM tbl;  -- reset sequence

COMMIT;
_

上記の欠点。

3.一時テーブル、TRUNCATEINSERT

RAMが十分にある場合のもう1つのオプション。これは、最初の2つの方法のいくつかの利点を兼ね備えています。オプション1。とほぼ同じ速度で、膨らみのない手付かずの新しいテーブルを取得できますが、オプション2.
ただしドキュメントごと:

TRUNCATEは、他のテーブルからの外部キー参照があるテーブルでは使用できません。コマンド。このような場合に有効性をチェックするには、テーブルスキャンが必要です。

大胆な強調鉱山。

FK制約を一時的に削除し、データ変更CTEを使用してすべてのFK列を更新できます。

_SET temp_buffers = 500MB;   -- example value, see 1st link below

BEGIN;

CREATE TEMP TABLE tbl_tmp AS
SELECT row_number() OVER (ORDER BY id) AS new_id, *
FROM   tbl
ORDER  BY id;  -- order here to use index (if one exists)

-- drop FK constraints in other tables referencing this one
-- which takes out an exclusive lock on those tables

TRUNCATE tbl;

INSERT INTO tbl
SELECT new_id, data  -- list all columns in order
FROM tbl_tmp;        -- rely on established order in tbl_tmp
-- ORDER BY id;      -- only to be absolutely sure (not necessary)

--  example for table "fk_tbl" with FK column "fk_id"
UPDATE fk_tbl f
SET    fk_id = t.new_id  -- set to new ID
FROM   tbl_tmp t
WHERE  f.fk_id = t.id;   -- match on old ID

-- add FK constraints in other tables back

COMMIT;
_

関連、詳細付き:

9