2012-01-04 48 views
13

Tôi hiểu làm thế nào để sử dụng mệnh đề WITH cho các truy vấn đệ quy (!!), nhưng tôi gặp vấn đề hiểu biết của nó sử dụng chung/năng lượng.Hướng dẫn về cách sử dụng mệnh đề WITH trong SQL

Ví dụ truy vấn sau đây cập nhật một bản ghi có id được xác định bằng cách sử dụng một subquery trả lại id của bản ghi đầu tiên bởi dấu thời gian:

update global.prospect psp 
set status=status||'*' 
where psp.psp_id=(
      select p2.psp_id 
      from global.prospect p2 
      where p2.status='new' or p2.status='reset' 
      order by p2.request_ts 
      limit 1) 
returning psp.*; 

này sẽ là một ứng cử viên tốt cho việc sử dụng một wrapper WITH thay vì truy vấn phụ tương đối xấu? Nếu vậy, tại sao?

+0

Theo tài liệu, sử dụng 'WITH [RECURSIVE]' ở trên các câu lệnh 'INSERT' và' UPDATE' đã được thêm vào trong PostgreSQL 9.1. –

+0

@JoeyAdams - sử dụng với dml - một lớp khác của hành tây để hiểu –

Trả lời

18

Nếu có thể có đồng thời ghi vào các bảng tham gia, có những điều kiện chủng tộc trong các truy vấn sau đây trên. Xem xét:


Ví dụ bạn có thể sử dụng một CTE (biểu thức bảng chung), nhưng nó sẽ cung cấp cho bạn không có gì một subquery không thể làm được:

WITH x AS (
    SELECT psp_id 
    FROM global.prospect 
    WHERE status IN ('new', 'reset') 
    ORDER BY request_ts 
    LIMIT 1 
    ) 
UPDATE global.prospect psp 
SET status = status || '*' 
FROM x 
WHERE psp.psp_id = x.psp_id 
RETURNING psp.*; 

BTW, hàng được trả về sẽ là cập nhật phiên bản.


Nếu bạn muốn chèn hàng trở lại vào bảng khác, đó là nơi mà một mệnh đề WITH trở nên cần thiết:

WITH x AS (
    SELECT psp_id 
    FROM global.prospect 
    WHERE status IN ('new', 'reset') 
    ORDER BY request_ts 
    LIMIT 1 
    ), y AS (
    UPDATE global.prospect psp 
    SET status = status || '*' 
    FROM x 
    WHERE psp.psp_id = x.psp_id 
    RETURNING psp.* 
    ) 
INSERT INTO z 
SELECT * 
FROM y 

dữ liệu sửa đổi các truy vấn sử dụng CTE có thể xảy ra với PostgreSQL 9.1 hoặc cao hơn.
Đọc more in the excellent manual.

+0

wow - thực sự tốt - cảm ơn. đồng ý với chất lượng của pg doc, nhưng cho đến nay, CTE đã trở thành một trong những điều tuyệt vời khi tôi đọc về nó, nhưng trong thực tế chưa bao giờ có một xử lý về nó. hai ví dụ của bạn (tôi nghĩ!) đã giúp rất nhiều –

9

WITH cho phép bạn xác định "bảng tạm thời" để sử dụng trong truy vấn SELECT. Ví dụ, tôi gần đây đã viết một truy vấn như thế này, để tính toán thay đổi giữa hai bộ:

-- Let o be the set of old things, and n be the set of new things. 
WITH o AS (SELECT * FROM things(OLD)), 
    n AS (SELECT * FROM things(NEW)) 

-- Select both the set of things whose value changed, 
-- and the set of things in the old set but not in the new set. 
SELECT o.key, n.value 
    FROM o 
    LEFT JOIN n ON o.key = n.key 
    WHERE o.value IS DISTINCT FROM n.value 

UNION ALL 

-- Select the set of things in the new set but not in the old set. 
SELECT n.key, n.value 
    FROM o 
    RIGHT JOIN n ON o.key = n.key 
    WHERE o.key IS NULL; 

Bằng việc xác định các "bảng" on ở phía trên, tôi đã có thể tránh lặp lại các từ ngữ things(OLD)things(NEW).

Chắc chắn, chúng tôi có thể loại bỏ UNION ALL bằng cách sử dụng FULL JOIN, nhưng tôi không thể làm điều đó trong trường hợp cụ thể của mình.


Nếu tôi hiểu câu hỏi của bạn một cách chính xác, nó thực hiện điều này:

  • Tìm hàng lâu đời nhất ở global.prospect có tình trạng là 'mới' hay 'reset'.

  • Đánh dấu nó bằng cách thêm một dấu hoa thị để tình trạng của nó

  • Return hàng (bao gồm tinh chỉnh của chúng tôi để status).

Tôi không nghĩ rằng WITH sẽ đơn giản hóa mọi thứ trong trường hợp của bạn. Nó có thể là một chút thanh lịch hơn để sử dụng một điều khoản FROM, mặc dù:

update global.prospect psp 
set status = status || '*' 
from (select psp_id 
     from global.prospect 
     where status = 'new' or status = 'reset' 
     order by request_ts 
     limit 1 
     ) p2 
where psp.psp_id = p2.psp_id 
returning psp.*; 

chưa được kiểm tra. Cho tôi biết nếu nó hoạt động.

Đó là khá nhiều chính xác những gì bạn có đã có, ngoại trừ:

  • này có thể dễ dàng mở rộng để cập nhật nhiều hàng. Trong phiên bản của bạn, sử dụng biểu thức truy vấn con, truy vấn sẽ không thành công nếu truy vấn con được thay đổi để mang lại nhiều hàng.

  • Tôi không bí danh global.prospect trong truy vấn con, do đó dễ đọc hơn một chút. Vì điều này sử dụng mệnh đề FROM, bạn sẽ gặp lỗi nếu vô tình tham chiếu bảng đang được cập nhật.

  • Trong phiên bản của bạn, khái niệm subquery được gặp cho mỗi mục duy nhất. Mặc dù PostgreSQL nên tối ưu hóa này và chỉ đánh giá sự biểu hiện một lần, tối ưu hóa này sẽ biến mất nếu bạn vô tình tham khảo một cột trong psp hoặc thêm một biểu hiện không ổn định.

+0

Để cập nhật nhiều hàng, người ta sẽ không cần bất kỳ dạng truy vấn phụ hoặc CTE nào, chỉ: 'UPDATE global.prospect SET status = status || '*' WHERE status IN ('mới', 'đặt lại') RETURNING *; ' –

Các vấn đề liên quan