Làm cách nào để cập nhật + tham gia vào PostgreSQL?


508

Về cơ bản, tôi muốn làm điều này:

update vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id=s.id 
set v.price=s.price_per_vehicle;

Tôi khá chắc chắn rằng nó sẽ hoạt động trong MySQL (nền của tôi), nhưng nó dường như không hoạt động trong postgres. Lỗi tôi nhận được là:

ERROR:  syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
                                  ^

Chắc chắn có một cách dễ dàng để làm điều này, nhưng tôi không thể tìm thấy cú pháp thích hợp. Vì vậy, làm thế nào tôi sẽ viết điều này trong PostgreSQL?


5
Cú pháp của Postgres là khác nhau: postgresql.org/docs/8.1/static/sql-update.html
Marc B

4
phương tiện_vehicle, lô hàng_shipment? Đó là một quy ước đặt tên bảng thú vị
CodeAndCats

3
@CodeAndCats Haha ... có vẻ buồn cười phải không? Tôi nghĩ rằng tôi đã sử dụng Django vào thời điểm đó và các bảng được nhóm theo tính năng. Vì vậy, đã có một vehicles_*bảng xem , và một vài shipments_*bảng.
mở

Câu trả lời:


776

Các cú pháp CẬP NHẬT là:

[VỚI [RECURSIVE] with_query [, ...]]
CẬP NHẬT [CHỈ] bảng [[AS] bí danh]
    SET {cột = {biểu thức | DEFAULT} |
          (cột [, ...]) = ({biểu thức | DEFAULT} [, ...])} [, ...]
    [TỪ từ danh sách]
    [Điều kiện ở đâu | HIỆN TẠI CỦA con trỏ tên]
    [TRẢ LỜI * | output_expression [[AS] output_name] [, ...]]

Trong trường hợp của bạn, tôi nghĩ rằng bạn muốn điều này:

UPDATE vehicles_vehicle AS v 
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id 

Nếu bản cập nhật phụ thuộc vào toàn bộ danh sách các phép nối bảng, thì chúng có nằm trong phần CẬP NHẬT hoặc phần TỪ không?
ted.strauss

11
@ ted.strauss: TỪ có thể chứa danh sách các bảng.
Mark Byers

140

Hãy để tôi giải thích thêm một chút bằng ví dụ của tôi.

Nhiệm vụ: thông tin chính xác, trong đó những người bỏ học (học sinh sắp rời trường cấp hai) đã nộp đơn vào trường đại học sớm hơn so với việc họ nhận được chứng chỉ của trường (vâng, họ đã nhận được chứng chỉ sớm hơn so với khi được cấp (theo ngày chứng nhận được chỉ định). tăng ngày nộp đơn để phù hợp với ngày cấp chứng chỉ.

Như vậy. tuyên bố giống như MySQL tiếp theo:

UPDATE applications a
JOIN (
    SELECT ap.id, ab.certificate_issued_at
    FROM abiturients ab
    JOIN applications ap 
    ON ab.id = ap.abiturient_id 
    WHERE ap.documents_taken_at::date < ab.certificate_issued_at
) b
ON a.id = b.id
SET a.documents_taken_at = b.certificate_issued_at;

Trở nên giống như PostgreSQL theo cách như vậy

UPDATE applications a
SET documents_taken_at = b.certificate_issued_at         -- we can reference joined table here
FROM abiturients b                                       -- joined table
WHERE 
    a.abiturient_id = b.id AND                           -- JOIN ON clause
    a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE

Như bạn có thể thấy, mệnh đề JOINcủa truy vấn con ban đầu ONđã trở thành một trong những WHEREđiều kiện, được kết hợp bởi ANDnhững người khác, đã được chuyển từ truy vấn con không có thay đổi. Và không cần phải tự JOINbàn nữa (vì nó nằm trong truy vấn con).


16
Làm thế nào bạn sẽ tham gia một bảng thứ ba?
Growler

19
Bạn chỉ cần JOINnhư bình thường trong FROMdanh sách:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - Bạn không thể sử dụng THAM GIA ở đó, tôi chỉ cần kiểm tra. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, bạn không thể sử dụng THAM GIA trong CẬP NHẬT, nhưng có thể sử dụng nó trong from_listmệnh đề CẬP NHẬT (là phần mở rộng SQL của PostgreQuery). Ngoài ra, hãy xem ghi chú về việc tham gia các bảng hãy cẩn thận trên liên kết bạn cung cấp.
Envek

@Envek - Ah, cảm ơn bạn đã làm rõ, tôi đã bỏ lỡ điều đó.
Adrian Smith

130

Câu trả lời của Mark Byers là tối ưu trong tình huống này. Mặc dù trong các tình huống phức tạp hơn, bạn có thể lấy truy vấn chọn trả về các hàng và giá trị được tính toán và đính kèm nó vào truy vấn cập nhật như sau:

with t as (
  -- Any generic query which returns rowid and corresponding calculated values
  select t1.id as rowid, f(t2, t2) as calculatedvalue
  from table1 as t1
  join table2 as t2 on t2.referenceid = t1.id
)
update table1
set value = t.calculatedvalue
from t
where id = t.rowid

Cách tiếp cận này cho phép bạn phát triển và kiểm tra truy vấn đã chọn của mình và trong hai bước chuyển đổi nó thành truy vấn cập nhật.

Vì vậy, trong trường hợp của bạn, truy vấn kết quả sẽ là:

with t as (
    select v.id as rowid, s.price_per_vehicle as calculatedvalue
    from vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id = s.id 
)
update vehicles_vehicle
set price = t.calculatedvalue
from t
where id = t.rowid

Lưu ý rằng bí danh cột là bắt buộc nếu không PostgreSQL sẽ phàn nàn về sự không rõ ràng của tên cột.


1
Tôi thực sự thích cái này bởi vì tôi luôn lo lắng khi đưa "select" của mình ra khỏi đầu và thay thế nó bằng một "cập nhật", đặc biệt là với nhiều lần tham gia. Điều này làm giảm số lượng các bãi chứa SQL mà tôi phải làm trước khi cập nhật hàng loạt. :)
dannysauer

5
Không chắc chắn tại sao, nhưng phiên bản CTE của truy vấn này nhanh hơn nhiều so với các giải pháp "tham gia đơn giản" ở trên
paul.ago

Ưu điểm khác của giải pháp này là khả năng tham gia từ nhiều hơn hai bảng để đến giá trị được tính toán cuối cùng của bạn bằng cách sử dụng nhiều phép nối trong câu lệnh with / select.
Alex Muro

1
Điều này thật tuyệt. Tôi đã chọn thủ công và giống như @dannysauer, tôi sợ chuyển đổi. Điều này chỉ đơn giản là làm điều đó cho tôi. Hoàn hảo!
băng giá

1
Ví dụ SQL đầu tiên của bạn có lỗi cú pháp. "update t1" không thể sử dụng bí danh từ truy vấn con t, nó cần sử dụng tên bảng: "update table1". Bạn làm điều này một cách chính xác trong ví dụ thứ hai của bạn.
EricS

80

Đối với những người thực sự muốn làm một JOINbạn cũng có thể sử dụng:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id;

Bạn có thể sử dụng a_alias trong SETphần bên phải dấu bằng nếu cần. Các trường ở bên trái của dấu bằng không yêu cầu tham chiếu bảng vì chúng được coi là từ bảng "a" ban đầu.


4
Xem xét đây là câu trả lời đầu tiên với sự tham gia thực sự (và không phải trong câu hỏi phụ), đây phải là câu trả lời thực sự được chấp nhận. Có thể đổi tên hoặc câu hỏi này để tránh nhầm lẫn cho dù postgresql có hỗ trợ tham gia cập nhật hay không.
vòng cổ

Nó hoạt động. Nhưng cảm thấy như một vụ hack
M. Habib

Cần lưu ý rằng theo tài liệu ( postgresql.org/docs/11/sql-update.html ), việc liệt kê bảng mục tiêu trong mệnh đề from sẽ khiến bảng mục tiêu tự tham gia. Ít tự tin hơn, tôi cũng thấy rằng đây là một sự tự tham gia chéo, có thể có kết quả ngoài ý muốn và / hoặc ý nghĩa về hiệu suất.
Ben Collins

14

Đối với những người muốn thực hiện THAM GIA cập nhật CHỈ các hàng mà tham gia của bạn trả về sử dụng:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id
--the below line is critical for updating ONLY joined rows
AND a.pk_id = a_alias.pk_id;

Điều này đã được đề cập ở trên nhưng chỉ thông qua một bình luận. Vì vậy, điều quan trọng là nhận được kết quả chính xác khi đăng câu trả lời MỚI mà Hoạt động


7

Ở đây chúng tôi đi:

update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;

Đơn giản như tôi có thể làm cho nó. Cảm ơn các bạn!

Cũng có thể làm điều này:

-- Doesn't work apparently
update vehicles_vehicle 
set price=s.price_per_vehicle
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id;

Nhưng sau đó, bạn đã có bảng xe ở đó hai lần và bạn chỉ được phép đặt bí danh một lần và bạn không thể sử dụng bí danh trong phần "đặt".


@littlegreen Bạn có chắc về điều đó? Không phải là joinhạn chế nó?
mở cửa

4
@mpen Tôi có thể xác nhận rằng nó cập nhật tất cả các bản ghi thành một giá trị. nó không làm những gì bạn mong đợi
Adam Bell

1

Đây là một SQL đơn giản cập nhật Mid_Name trên bảng Name3 bằng cách sử dụng trường Middle_Name từ Tên:

update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;


0

Tên bảng đầu tiên: tbl_table1 (tab1). Tên bảng thứ hai: tbl_table2 (tab2).

Đặt cột ac_status của tbl_table1 thành "INACTIVE"

update common.tbl_table1 as tab1
set ac_status= 'INACTIVE' --tbl_table1's "ac_status"
from common.tbl_table2 as tab2
where tab1.ref_id= '1111111' 
and tab2.rel_type= 'CUSTOMER';
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.