2012-01-04 26 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