2012-01-04 28 views
13

我知道如何使用遞歸查詢(!!)的WITH子句,但我在理解其一般用法/功能時遇到問題。關於在SQL中使用WITH子句的指導

例如下面的查詢更新一個記錄,其ID是通過使用子查詢通過時間戳返回的第一條記錄的ID決定:

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.*; 

請問這是使用WITH包裝,而不是一個很好的候選人比較醜陋的子查詢?如果是這樣,爲什麼?

+0

根據文檔,在PostgreSQL 9.1中添加了在INSERT和UPDATE語句之上使用WITH [RECURSIVE]。 – 2012-01-04 04:08:20

+0

@JoeyAdams - 使用dml - 理解另一層洋蔥 – 2012-01-04 04:16:34

回答

18

如果可以有併發寫入訪問對涉及的表,有在上述以下查詢競爭條件。試想一下:


你的榜樣可以使用CTE(公共表表達式),但它會給你什麼子查詢不能這樣做:

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.*; 

順便說一句,返回的行將是更新的版本。


如果你想返回的行插入到另一個表,這就是一個WITH子句就顯得至關重要:

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 

數據使用CTE修改查詢都可以在PostgreSQL 9.1或更高版本。
閱讀more in the excellent manual

+0

哇 - 真的很好 - 謝謝。同意pg doc的質量,但迄今爲止,CTE已經成爲閱讀這些內容時聽起來很酷的東西之一,但實際上從來沒有處理過它。你的兩個例子(我想!)有很大的幫助 – 2012-01-04 14:49:11

9

WITH允許您定義用於SELECT查詢的「臨時表」。比如,我最近寫了這樣的查詢,兩組之間計算的變化:

-- 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; 

通過定義「表」 on在上面,我是能夠避免重複表達things(OLD)things(NEW)

當然,我們可以使用FULL JOIN來消除UNION ALL,但在我的特殊情況下我無法做到這一點。


如果我正確理解您的查詢,它這樣做:

  • 查找global.prospect其狀態是「新」或「復位」最古老的行。

  • 馬克它通過增加一個星號,其狀態

  • 返回行(包括我們的調整,以status)。

我不認爲WITH會簡化你的情況。它可能會稍微更優雅的使用FROM條款,但:

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.*; 

未經檢驗。讓我知道它是否有效。

這幾乎是你有什麼已經,除了:

  • 這可以很容易地擴展到更新多行。在使用子查詢表達式的版本中,如果子查詢更改爲產生多行,則查詢將失敗。

  • 我沒有在子查詢中別名global.prospect,所以它更容易閱讀。由於這使用了FROM子句,因此如果意外引用正在更新的表,則會出現錯誤。

  • 在您的版本中,每個單項都會遇到子查詢表達式。雖然PostgreSQL應該對此進行優化並且只對表達式進行一次評估,但如果您不小心引用psp中的列或添加易失性表達式,則此優化將消失。

+0

要更新多行,不需要任何形式的子查詢或CTE,只需:'UPDATE global.prospect SET status = status || '*' WHERE狀態IN('new','reset')RETURNING *;' – 2012-01-04 10:26:13