Chèn dữ liệu vào 3 bảng cùng một lúc bằng Postgres


81

Tôi muốn chèn dữ liệu vào 3 bảng với một truy vấn duy nhất.
Các bảng của tôi trông giống như dưới đây:

CREATE TABLE sample (
   id        bigserial PRIMARY KEY,
   lastname  varchar(20),
   firstname varchar(20)
);

CREATE TABLE sample1(
   user_id    bigserial PRIMARY KEY,
   sample_id  bigint REFERENCES sample,
   adddetails varchar(20)
);

CREATE TABLE sample2(
   id      bigserial PRIMARY KEY,
   user_id bigint REFERENCES sample1,
   value   varchar(10)
);

Đổi lại tôi sẽ nhận được một chìa khóa cho mỗi lần chèn và tôi cần phải đưa chìa khóa đó vào bảng tiếp theo.
Truy vấn của tôi là:

insert into sample(firstname,lastname) values('fai55','shaggk') RETURNING id;
insert into sample1(sample_id, adddetails) values($id,'ss') RETURNING user_id;
insert into sample2(user_id, value) values($id,'ss') RETURNING id;

Nhưng nếu tôi chạy các truy vấn đơn lẻ, chúng chỉ trả về giá trị cho tôi và tôi không thể sử dụng lại chúng trong truy vấn tiếp theo ngay lập tức.

Làm thế nào để đạt được điều này?

Câu trả lời:


132

Sử dụng CTE sửa đổi dữ liệu :

WITH ins1 AS (
   INSERT INTO sample(firstname, lastname)
   VALUES ('fai55', 'shaggk')
-- ON     CONFLICT DO NOTHING         -- optional addition in Postgres 9.5+
   RETURNING id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT sample_id, 'ss' FROM ins1
   RETURNING user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT user_id, 'ss2' FROM ins2;

Mỗi cái INSERTphụ thuộc vào cái trước. SELECTthay vì VALUESđảm bảo rằng không có gì được chèn vào các bảng phụ nếu không có hàng nào được trả về từ trước đó INSERT. (Vì Postgres 9.5+, bạn có thể thêm dấu ON CONFLICT.)
Theo cách này, nó cũng ngắn hơn và nhanh hơn một chút.

Thông thường, sẽ thuận tiện hơn khi cung cấp các hàng dữ liệu hoàn chỉnh ở một nơi :

WITH data(firstname, lastname, adddetails, value) AS (
   VALUES                              -- provide data here
      ('fai55', 'shaggk', 'ss', 'ss2') -- see below
    , ('fai56', 'XXaggk', 'xx', 'xx2') -- works for multiple input rows
       --  more?                      
   )
, ins1 AS (
   INSERT INTO sample (firstname, lastname)
   SELECT firstname, lastname          -- DISTINCT? see below
   FROM   data
   -- ON     CONFLICT DO NOTHING       -- UNIQUE constraint? see below
   RETURNING firstname, lastname, id AS sample_id
   )
, ins2 AS (
   INSERT INTO sample1 (sample_id, adddetails)
   SELECT ins1.sample_id, d.adddetails
   FROM   data d
   JOIN   ins1 USING (firstname, lastname)
   RETURNING sample_id, user_id
   )
INSERT INTO sample2 (user_id, value)
SELECT ins2.user_id, d.value
FROM   data d
JOIN   ins1 USING (firstname, lastname)
JOIN   ins2 USING (sample_id);

db <> fiddle here

Bạn có thể cần truyền kiểu rõ ràng trong một VALUESbiểu thức độc lập - trái ngược với một VALUESbiểu thức được đính kèm với một INSERTnơi các kiểu dữ liệu được lấy từ bảng đích. Xem:

Nếu nhiều hàng có thể giống hệt nhau (firstname, lastname), bạn có thể cần phải gấp các hàng trùng lặp cho hàng đầu tiên INSERT:

...
INSERT INTO sample (firstname, lastname)
SELECT DISTINCT firstname, lastname FROM data
...

Bạn có thể sử dụng bảng (tạm thời) làm nguồn dữ liệu thay vì CTE data.

Có lẽ sẽ hợp lý nếu kết hợp điều này với một ràng buộc DUY NHẤT trên (firstname, lastname)bảng và một ON CONFLICTmệnh đề trong truy vấn.

Có liên quan:


1
thanx cho phát lại tôi có thể thêm cuộn giao dịch ra nếu có thất bại chèn xảy ra .yes thế nào tôi có thể
Faisal

3
Đây là một câu lệnh SQL đơn. Người ta có thể gộp nhiều câu lệnh vào một giao dịch duy nhất, nhưng không thể tách câu lệnh này ra. Ngoài ra, những gì Denis nói trong bình luận của mình. Và tôi đã thêm một số liên kết vào câu trả lời của mình.
Erwin Brandstetter 13/12/13

2
@mmcrae: Có, bạn có thể. Liên quan: dba.stackexchange.com/questions/151199/...
Erwin Brandstetter

1
@No_name: chắc chắn rồi, nhiều cách khác nhau. Tôi đề nghị bạn đặt một câu hỏi với các chi tiết xác định. bạn luôn có thể liên kết ở đây để biết ngữ cảnh. hoặc thả một bình luận ở đây liên kết trở lại để thu hút sự chú ý của tôi.
Erwin Brandstetter

1
Đây có phải là lỗi đánh máy không? Trong câu trả lời của bạn INSERT INTO sample1 (user_id, adddetails), có nên không (sample_id, addetails)?
Adam Hughes

17

Một cái gì đó như thế này

with first_insert as (
   insert into sample(firstname,lastname) 
   values('fai55','shaggk') 
   RETURNING id
), 
second_insert as (
  insert into sample1( id ,adddetails) 
  values
  ( (select id from first_insert), 'ss')
  RETURNING user_id
)
insert into sample2 ( id ,adddetails) 
values 
( (select user_id from first_insert), 'ss');

Vì id được tạo từ phần chèn vào sample2là không cần thiết, tôi đã xóa returningmệnh đề khỏi phần chèn cuối cùng.


Tôi thích cách tiếp cận này với các giá trị bên trong được chọn. Đó là ổn định hơn và cũng có thể thả các bí danh trở lại bên trong với báo cáo
mattdlockyer

6

Thông thường, bạn sẽ sử dụng một giao dịch để tránh viết các truy vấn phức tạp.

http://www.postgresql.org/docs/current/static/sql-begin.html

http://dev.mysql.com/doc/refman/5.7/en/commit.html

Bạn cũng có thể sử dụng CTE, giả sử thẻ Postgres của bạn là chính xác. Ví dụ:

with sample_ids as (
  insert into sample(firstname, lastname)
  values('fai55','shaggk')
  RETURNING id
), sample1_ids as (
  insert into sample1(id, adddetails)
  select id,'ss'
  from sample_ids
  RETURNING id, user_id
)
insert into sample2(id, user_id, value)
select id, user_id, 'val'
from sample1_ids
RETURNING id, user_id;

1
thanx làm thế nào để tôi đạt được giao dịch trong truy vấn này nếu bất kỳ chèn nào không thành công, tôi có thể thực hiện khôi phục
Faisal

1
Sau đó, bạn bắt đầu lại mọi thứ, tất nhiên sau khi sửa các truy vấn, vì toàn bộ giao dịch (hoặc cte) sẽ được khôi phục lại. Btw, nếu các lần chèn của bạn thỉnh thoảng không thành công, có thể bạn đang làm gì đó sai. Trường hợp duy nhất hợp lý để việc chèn không thành công là trong một tình huống nâng cấp gặp phải các khóa duy nhất trùng lặp trong các giao dịch đồng thời và thậm chí sau đó bạn có thể nhận được một khóa cố vấn hoặc khóa bảng nếu bạn cần làm mọi thứ bằng cách chống đạn.
Denis de Bernardy

3

Bạn có thể tạo một trình kích hoạt sau khi chèn trên bảng Mẫu để chèn vào hai bảng khác.

Vấn đề duy nhất tôi gặp khi thực hiện việc này là bạn sẽ không có cách nào để chèn các adddetails, nó sẽ luôn trống hoặc trong trường hợp này là ss. Không có cách nào để chèn một cột vào mẫu không thực tế trong bảng mẫu, do đó bạn không thể gửi nó cùng với chèn bẩm sinh.

Một tùy chọn khác là tạo một thủ tục được lưu trữ để chạy các phụ trang của bạn.

Bạn có câu hỏi được gắn thẻ mysql và postgressql mà chúng ta đang nói về cơ sở dữ liệu nào ở đây?

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.