Đồng bằng
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
Việc sử dụng LEFT [OUTER] JOINthay thế [INNER] JOINcó nghĩa là các hàng từ val không bị bỏ khi không tìm thấy kết quả khớp foo. Thay vào đó, NULLđược nhập cho foo_id.
Các VALUESbiểu hiện trong subquery không giống như @ ypercube của CTE. Biểu thức bảng chung cung cấp các tính năng bổ sung và dễ đọc hơn trong các truy vấn lớn, nhưng chúng cũng đặt ra các rào cản tối ưu hóa. Vì vậy, các truy vấn con thường nhanh hơn một chút khi không cần điều nào ở trên.
idnhư tên cột là một mô hình chống lan rộng. Nên foo_idvà bar_idhoặc bất cứ điều gì mô tả. Khi tham gia một loạt các bảng, bạn kết thúc với nhiều cột được đặt tên id...
Xem xét đơn giản texthoặc varcharthay vì varchar(n). Nếu bạn thực sự cần áp đặt giới hạn độ dài, hãy thêm một CHECKràng buộc:
Bạn có thể cần thêm phôi loại rõ ràng. Vì VALUESbiểu thức không được gắn trực tiếp vào bảng (như trong INSERT ... VALUES ...), các loại dữ liệu không thể được dẫn xuất và các kiểu dữ liệu mặc định được sử dụng mà không cần khai báo kiểu rõ ràng, có thể không hoạt động trong mọi trường hợp. Nó là đủ để làm điều đó trong hàng đầu tiên, phần còn lại sẽ xếp hàng.
CHỌN thiếu các hàng FK cùng một lúc
Nếu bạn muốn tạo các mục không tồn tại foomột cách nhanh chóng, trong một câu lệnh SQL , CTE là công cụ:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
Lưu ý hai hàng giả mới để chèn. Cả hai đều có màu tím , chưa tồn tại foo. Hai hàng để minh họa sự cần thiết DISTINCTtrong INSERTcâu lệnh đầu tiên .
Giải thích từng bước
CTE đầu tiên selcung cấp nhiều hàng dữ liệu đầu vào. Truy vấn con valvới VALUESbiểu thức có thể được thay thế bằng bảng hoặc truy vấn con dưới dạng nguồn. Ngay lập tức LEFT JOINđể foonối thêm foo_idcho các typehàng có sẵn . Tất cả các hàng khác có được foo_id IS NULLtheo cách này.
CTE thứ 2 inschèn các loại mới ( ) khác biệtfoo_id IS NULL vào foovà trả về kiểu mới được tạo foo_id- cùng với việc typenối lại để chèn các hàng.
INSERTBây giờ bên ngoài cuối cùng có thể chèn một foo.id cho mỗi hàng: loại tồn tại trước hoặc nó được chèn vào bước 2.
Nói đúng ra, cả hai phần chèn đều xảy ra "song song", nhưng vì đây là một câu lệnh duy nhất , các FOREIGN KEYràng buộc mặc định sẽ không phàn nàn. Tính toàn vẹn tham chiếu được thi hành ở cuối câu lệnh theo mặc định.
Fiddle SQL cho Postgres 9.3. (Hoạt động tương tự trong 9.1.)
Có một điều kiện cuộc đua nhỏ nếu bạn chạy đồng thời nhiều truy vấn này. Đọc thêm dưới các câu hỏi liên quan ở đây và ở đây và ở đây . Thực sự chỉ xảy ra dưới tải nặng đồng thời, nếu có bao giờ. So với các giải pháp bộ nhớ đệm như được quảng cáo trong một câu trả lời khác, cơ hội là siêu nhỏ .
Chức năng sử dụng nhiều lần
Để sử dụng lặp đi lặp lại, tôi sẽ tạo một hàm SQL lấy một mảng các bản ghi làm tham số và sử dụng unnest(param)thay cho VALUESbiểu thức.
Hoặc, nếu cú pháp cho mảng các bản ghi quá lộn xộn đối với bạn, hãy sử dụng chuỗi được phân tách bằng dấu phẩy làm tham số _param. Ví dụ về mẫu:
'description1,type1;description2,type2;description3,type3'
Sau đó sử dụng điều này để thay thế VALUESbiểu thức trong tuyên bố trên:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Chức năng với UPSERT trong Postgres 9.5
Tạo một loại hàng tùy chỉnh để truyền tham số. Chúng ta có thể làm mà không cần nó, nhưng nó đơn giản hơn:
CREATE TYPE foobar AS (description text, type text);
Chức năng:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
Gọi:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
Nhanh chóng và vững chắc cho môi trường với các giao dịch đồng thời.
Ngoài các truy vấn trên, ...
... áp dụng SELECThoặc INSERTbật foo: Bất kỳ typecái nào không tồn tại trong bảng FK, được chèn vào. Giả sử hầu hết các loại tồn tại trước. Để hoàn toàn chắc chắn và loại trừ các điều kiện cuộc đua, các hàng hiện tại chúng tôi cần đã bị khóa (để các giao dịch đồng thời không thể can thiệp). Nếu đó là quá hoang tưởng cho trường hợp của bạn, bạn có thể thay thế:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
với
ON CONFLICT(type) DO NOTHING
... áp dụng INSERThoặc UPDATE(đúng "UPSERT") trên bar: Nếu descriptionđã tồn tại thì nó typeđược cập nhật:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
Nhưng chỉ khi typethực sự thay đổi:
... chuyển các giá trị như các loại hàng nổi tiếng với một VARIADICtham số. Lưu ý tối đa mặc định là 100 tham số! So sánh:
Có nhiều cách khác để vượt qua nhiều hàng ...
Liên quan: