Nén một chuỗi trong PostgreSQL


9

Tôi có một id serial PRIMARY KEYcột trong bảng PostgreSQL. Nhiều ids bị thiếu vì tôi đã xóa hàng tương ứng.

Bây giờ tôi muốn "thu gọn" bảng bằng cách khởi động lại chuỗi và gán lại các ids theo cách mà idthứ tự ban đầu được giữ nguyên. Có thể không?

Thí dụ:

  • Hiện nay:

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

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

Tôi đã thử những gì được đề xuất trong câu trả lời StackOverflow , nhưng nó không hoạt động:

# 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.

Câu trả lời:


9

Trước hết, khoảng trống trong một chuỗi sẽ được dự kiến. Tự hỏi nếu bạn thực sự cần phải loại bỏ chúng. Cuộc sống của bạn trở nên đơn giản hơn nếu bạn chỉ sống với nó. Để có được các số không có khoảng cách, phương án (thường tốt hơn) là sử dụng a VIEWvới row_number(). Ví dụ trong câu trả lời liên quan này:

Dưới đây là một số công thức để loại bỏ khoảng cách.

1. Bảng mới, nguyên sơ

Tránh các biến chứng với các vi phạm duy nhất và phình bảng và nhanh chóng . Chỉ dành cho các trường hợp đơn giản mà bạn không bị ràng buộc bởi các tham chiếu FK, các khung nhìn trên bảng hoặc các đối tượng tùy thuộc khác hoặc bởi truy cập đồng thời. Thực hiện trong một giao dịch để tránh tai nạn:

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)sao chép cấu trúc bao gồm. các ràng buộc và mặc định từ bảng gốc. Sau đó, tạo cột bảng mới sở hữu chuỗi:

Và đặt lại về mức tối đa mới:

Điều này mang lại lợi thế là bảng mới không bị phình to và co cụm id.

2. UPDATEtại chỗ

Điều này tạo ra rất nhiều hàng chết và yêu cầu (tự động) VACUUMsau này.

Nếu serialcột cũng là PRIMARY KEY(như trong trường hợp của bạn) hoặc có một UNIQUEràng buộc, bạn phải tránh các vi phạm duy nhất trong quy trình. Mặc định (rẻ hơn) cho các ràng buộc PK / UNIQUE là NOT DEFERRABLE, điều này buộc kiểm tra sau mỗi hàng đơn. Tất cả các chi tiết theo câu hỏi liên quan này trên SO:

Bạn có thể định nghĩa ràng buộc của mình là DEFERRABLE(điều này làm cho nó đắt hơn).
Hoặc bạn có thể bỏ các ràng buộc và thêm lại khi bạn hoàn thành:

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;

Không thể trong khi bạn có cácFOREIGN KEYràng buộc tham chiếu (các) cột vì ( trên mỗi tài liệu ):

Các cột được tham chiếu phải là các cột của ràng buộc khóa chính hoặc duy nhất không thể bảo vệ trong bảng được tham chiếu.

Bạn sẽ cần (khóa tất cả các bảng có liên quan và) thả / tạo lại các ràng buộc FK và cập nhật tất cả các giá trị FK theo cách thủ công (xem tùy chọn 3. ). Hoặc bạn phải di chuyển các giá trị ra khỏi đường trong một giây UPDATEđể tránh xung đột. Chẳng hạn, giả sử bạn không có số âm:

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;

Hạn chế như đã đề cập ở trên.

3. Bảng tạm thời TRUNCATE,,INSERT

Thêm một lựa chọn nếu bạn có nhiều RAM. Điều này kết hợp một số lợi thế của hai cách đầu tiên. Gần như nhanh như tùy chọn 1. và bạn có được một bảng mới, nguyên sơ không phình to nhưng vẫn giữ tất cả các ràng buộc và phụ thuộc như trong tùy chọn 2.
Tuy nhiên , theo mỗi tài liệu:

TRUNCATE không thể được sử dụng trên một bảng có tham chiếu khóa ngoài từ các bảng khác, trừ khi tất cả các bảng như vậy cũng bị cắt ngắn trong cùng một lệnh. Kiểm tra tính hợp lệ trong các trường hợp như vậy sẽ yêu cầu quét bảng và toàn bộ điểm không được thực hiện.

Nhấn mạnh đậm của tôi.

Bạn có thể tạm thời bỏ các ràng buộc FK và sử dụng các CTE sửa đổi dữ liệu để cập nhật tất cả các cột 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;

Liên quan, với nhiều chi tiết hơn:


Nếu tất cả FOREIGN KEYSđược đặt thành CASCADEkhông thể bạn chỉ cần lặp qua các khóa chính cũ và cập nhật giá trị của chúng tại chỗ (từ giá trị cũ sang giá trị mới)? Về cơ bản, đây là tùy chọn 3 mà không cần TRUNCATE tblthay thế INSERTbằng một UPDATEvà không cần cập nhật khóa ngoại theo cách thủ công.
Gili

@Gili: Bạn có thể , nhưng loại vòng lặp đó cực kỳ tốn kém. Vì bạn không thể cập nhật toàn bộ bảng cùng một lúc do vi phạm khóa duy nhất trong chỉ mục, nên bạn cần một UPDATElệnh riêng cho mỗi hàng. Xem giải thích trong hoặc thử và tự mình xem.
Erwin Brandstetter

Tôi không nghĩ rằng hiệu suất là một vấn đề trong trường hợp của tôi. Theo cách tôi nhìn thấy, có hai loại thuật toán: những thuật toán "ngăn chặn thế giới" và những thuật toán chạy lặng lẽ trong nền mà không cần phải gỡ xuống máy chủ. Giả sử rằng quá trình nén chỉ xảy ra một lần trong một mặt trăng xanh (ví dụ: khi đạt đến giới hạn trên của loại dữ liệu), thực sự không có giới hạn trên về thời gian cần thiết. Miễn là chúng tôi thu gọn các bản ghi nhanh hơn các bản ghi mới đang được thêm vào, chúng tôi sẽ ổn.
Gili
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.