Hàng trùng lặp với Khóa chính trong PostgreSQL


7

Giả sử tôi có một bảng như sau people, được đặt tên idlà Khóa chính:

+-----------+---------+---------+
|  id       |  fname  |  lname  |
| (integer) | (text)  | (text)  |
+===========+=========+=========+
|  1        | Daniel  | Edwards |
|  2        | Fred    | Holt    |
|  3        | Henry   | Smith   |
+-----------+---------+---------+

Tôi đang cố gắng viết một truy vấn trùng lặp hàng đủ mạnh để tính đến các thay đổi lược đồ cho bảng. Bất cứ khi nào tôi thêm một cột vào bảng, tôi không muốn phải quay lại và sửa đổi truy vấn trùng lặp.

Tôi biết tôi có thể làm điều này, nó sẽ nhân đôi bản ghi id 2 và cung cấp cho bản ghi trùng lặp một id mới:

INSERT INTO people (fname, lname) SELECT fname, lname FROM people WHERE id = 2;

Tuy nhiên, nếu tôi thêm một agecột, tôi sẽ cần sửa đổi truy vấn thành tài khoản cho cột tuổi.

Rõ ràng tôi không thể làm như sau, vì nó cũng sẽ nhân đôi khóa chính, dẫn đến một duplicate key value violates unique constraint- Và, tôi không muốn họ chia sẻ cùng một id:

INSERT INTO people SELECT * FROM people WHERE id = 2

Với những gì đã nói, điều gì sẽ là một cách tiếp cận hợp lý để giải quyết thách thức này? Tôi muốn tránh xa các thủ tục được lưu trữ, nhưng tôi không chống lại họ 100%, tôi cho rằng ...


1
Ngoài ra: có lẽ bạn nên sử dụng một ví dụ khác, vì đây agelà một kiểu chống mẫu cho một cột. (Một người nên lưu trữ birthday.)
Erwin Brandstetter

Câu trả lời:


15

Đơn giản với hstore

Nếu bạn đã hstorecài đặt mô-đun bổ sung ( hướng dẫn trong liên kết bên dưới ), có một cách đơn giản đáng ngạc nhiên để thay thế (các) giá trị của (các) trường riêng lẻ mà không biết gì về các cột khác:

Ví dụ cơ bản: nhân đôi hàng bằng id = 2nhưng thay thế 2bằng 3:

INSERT INTO people
SELECT (p #= hstore('id', '3')).* FROM people p WHERE id = 2;

Chi tiết:

Giả sử (vì nó không được xác định trong câu hỏi) đópeople.idlà mộtserialcột có trình tự đính kèm, bạn sẽ muốn giá trị tiếp theo từ chuỗi. Chúng ta có thể xác định tên trình tự vớipg_get_serial_sequence(). Chi tiết:

Hoặc bạn chỉ có thể mã hóa tên trình tự nếu nó sẽ không thay đổi.
Chúng tôi sẽ có truy vấn này:

INSERT INTO people
SELECT (p #= hstore('id', nextval(pg_get_serial_sequence('people', 'id'))::text)).*
FROM people p WHERE id = 2;

Cái nào hoạt động , nhưng bị một điểm yếu trong trình lập kế hoạch truy vấn Postgres: Biểu thức được ước tính riêng cho từng cột trong hàng, lãng phí số thứ tự và hiệu suất. Để tránh điều này, hãy di chuyển biểu thức thành một biểu mẫu con và chỉ phân tách hàng một lần :

INSERT INTO people
SELECT (p1).*
FROM  (
   SELECT p #= hstore('id', nextval(pg_get_serial_sequence('people', 'id'))::text) AS p1
   FROM   people p WHERE id = 2
   ) sub;

Có thể nhanh nhất cho một (hoặc vài) hàng (s) cùng một lúc.

json / jsonb

Nếu bạn chưa hstorecài đặt và không thể cài đặt các mô-đun bổ sung, bạn có thể thực hiện một mẹo tương tự với json_populate_record()hoặc jsonb_populate_record(), nhưng khả năng đó không có giấy tờ và có thể không đáng tin cậy.

Bảng tạm thời

Một giải pháp đơn giản khác là sử dụng tạm thời như thế này:

BEGIN;
CREATE TEMP TABLE people_tmp ON COMMIT DROP AS
SELECT * FROM people WHERE id = 2;
UPDATE people_tmp SET id = nextval(pg_get_serial_sequence('people', 'id'));
INSERT INTO people TABLE people_tmp;
COMMIT;

Tôi đã thêm ON COMMIT DROPđể thả bảng tự động vào cuối giao dịch. Do đó, tôi cũng kết thúc hoạt động thành một giao dịch của riêng mình. Không phải là hoàn toàn cần thiết.

Điều này cung cấp một loạt các tùy chọn bổ sung - bạn có thể làm bất cứ điều gì với hàng trước khi chèn, nhưng nó sẽ chậm hơn một chút do chi phí tạo và thả bảng tạm thời.

Giải pháp này hoạt động cho một hàng đơn hoặc cho bất kỳ số lượng hàng nào cùng một lúc . Mỗi hàng tự động nhận một giá trị mặc định mới từ chuỗi.

Sử dụng ký hiệu ngắn (tiêu chuẩn SQL)TABLE people .

SQL động

Đối với nhiều hàng cùng một lúc, SQL động sẽ nhanh nhất. Nối các cột từ bảng hệ thống pg_attributehoặc từ lược đồ thông tin và thực hiện nó một cách linh hoạt trong một DOcâu lệnh hoặc viết một hàm để sử dụng nhiều lần:

CREATE OR REPLACE FUNCTION f_row_copy(_tbl regclass, _id int, OUT row_ct int) AS
$func$
BEGIN
   EXECUTE (
      SELECT format('INSERT INTO %1$s(%2$s) SELECT %2$s FROM %1$s WHERE id = $1',
                    _tbl, string_agg(quote_ident(attname), ', '))
      FROM   pg_attribute
      WHERE  attrelid = _tbl
      AND    NOT attisdropped  -- no dropped (dead) columns
      AND    attnum > 0        -- no system columns
      AND    attname <> 'id'   -- exclude id column
      )
   USING _id;

   GET DIAGNOSTICS row_ct = ROW_COUNT;  -- directly assign OUT parameter
END
$func$  LANGUAGE plpgsql;

Gọi:

SELECT f_row_copy('people', 9);

Hoạt động cho bất kỳ bảng với một cột số nguyên có tên id. Bạn cũng có thể dễ dàng đặt tên cột động ...

Có thể không phải là lựa chọn đầu tiên của bạn vì bạn muốn stay away from stored procedures, nhưng một lần nữa, đó không phải là "thủ tục được lưu trữ" ...

Liên quan:

Giải pháp tiên tiến

Một serialcột là một trường hợp đặc biệt. Nếu bạn muốn điền nhiều hơn hoặc tất cả các cột với các giá trị mặc định tương ứng của chúng, thì nó sẽ phức tạp hơn. Hãy xem xét câu trả lời liên quan này:


hstoreCách tiếp cận hoạt động rất tốt, tuy nhiên tôi nghĩ rằng tôi sẽ chơi xung quanh với jsonbcách tiếp cận vì tôi đã phụ thuộc rất nhiều vào nó. Cảm ơn rất nhiều vì đã viết lên, Erwin!
Joshua Burns

Bảng tạm thời hoạt động thực sự tốt cho tôi khi tôi cần sao chép một khối các bản ghi trong khi thay đổi một trường khác. ... WHERE fieldA=1 ... SET ... fieldA=2 ...
Chris Nelson

0

Cố gắng tạo một triggerchèn trên:

CREATE TRIGGER name BEFORE INSERT

Trong trình kích hoạt này, bạn tạo ID NULL. Khi kích hoạt kết thúc, quá trình chèn được thực hiện và Postgres sẽ cung cấp ID. Tôi giả sử rằng bạn đã xác định ID là DEFAULT NEXTVAL('A_SEQUENCE'::REGCLASS).


2
Điều này sẽ hoạt động, nhưng đó là một giải pháp "Lén lút" mà tôi nghĩ rằng cuối cùng sẽ gây ra vấn đề. Cá nhân tôi sẽ tránh làm điều này nếu có thể .... Nếu anh ấy chọn làm điều này, tôi hy vọng anh ấy sẽ ĐẦU TIÊN thực hiện CHỌN bên trong trình kích hoạt để xem ID được cung cấp có tồn tại không .. nếu có, thì, và chỉ sau đó, đặt NEW.id thành NULL ..
Joishi Bodio

Anh ta có thể làm điều đó nhưng nếu bạn chọn sử dụng một NEXTVAL('A_SEQUENCE'::REGCLASS)cái mà bạn không bao giờ tự cung cấp ID cho một mục mới.
Marco

1
Điều đó phụ thuộc vào cách mã và / hoặc thư viện bên ngoài trong mã của bạn sẽ sử dụng cơ sở dữ liệu. Một số người có thể truy vấn SEQ.NEXTVAL theo cách thủ công và sau đó gửi ID được tạo trong câu lệnh INSERT .. Tôi sẽ không tin rằng bộ ba bảng / trình tự / trình kích hoạt sẽ kết thúc "như mong đợi" mọi lúc. Vì vậy, bình luận đầu tiên của tôi.
Joishi Bodio

-2

Dynamic SQL Work rất tuyệt, tôi đang tìm kiếm điều này từ vài năm nay,

nếu bạn có nhiều hơn một cột bị loại trừ, hãy thử đơn giản,

AND    attname <> 'id'   -- exclude id column
AND    attname <> 'second_col_name'   -- exclude second_col_name 
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.