Đồ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] JOIN
thay thế [INNER] JOIN
có 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 VALUES
biể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.
id
như tên cột là một mô hình chống lan rộng. Nên foo_id
và bar_id
hoặ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 text
hoặc varchar
thay 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 CHECK
ràng buộc:
Bạn có thể cần thêm phôi loại rõ ràng. Vì VALUES
biể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 foo
mộ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 DISTINCT
trong INSERT
câu lệnh đầu tiên .
Giải thích từng bước
CTE đầu tiên sel
cung cấp nhiều hàng dữ liệu đầu vào. Truy vấn con val
với VALUES
biể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
để foo
nối thêm foo_id
cho các type
hàng có sẵn . Tất cả các hàng khác có được foo_id IS NULL
theo cách này.
CTE thứ 2 ins
chèn các loại mới ( ) khác biệtfoo_id IS NULL
vào foo
và trả về kiểu mới được tạo foo_id
- cùng với việc type
nối lại để chèn các hàng.
INSERT
Bâ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 KEY
rà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 VALUES
biể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ế VALUES
biể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 SELECT
hoặc INSERT
bật foo
: Bất kỳ type
cá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 INSERT
hoặ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 type
thự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 VARIADIC
tham 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: