有关在SQL中使用WITH子句的指导

时间:2012-01-04 02:57:55

标签: sql postgresql sql-update common-table-expression

我理解如何使用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包装而不是相对丑陋的子查询?如果是这样,为什么?

2 个答案:

答案 0 :(得分:19)

如果可以对涉及的表进行并发写访问,则上述查询中存在竞争条件。考虑:


您的示例可以使用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

使用PostgreSQL 9.1或更高版本可以使用CTE进行数据修改查询 阅读more in the excellent manual

答案 1 :(得分:10)

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)

当然,我们可以使用UNION ALL删除FULL JOIN,但在我的特定情况下我无法做到这一点。


如果我正确理解您的查询,则执行以下操作:

  • 查找global.prospect中状态为“new”或“reset”的最旧行。

  • 通过在状态

  • 中添加星号来标记它
  • 返回行(包括我们的调整到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中的列或添加易失性表达式,则此优化将消失。