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

cc *_*ung 14 sql postgresql common-table-expression sql-update

我理解如何使用该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.*;
Run Code Online (Sandbox Code Playgroud)

这是否适合使用WITH包装器而不是相对丑陋的子查询?如果是这样,为什么?

Erw*_*ter 20

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


您的示例可以使用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.*;
Run Code Online (Sandbox Code Playgroud)

顺便说一下,返回的行将是更新版本.


如果要将返回的行插入另一个表,那么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
Run Code Online (Sandbox Code Playgroud)

使用PostgreSQL 9.1或更高版本可以使用CTE进行数据修改查询.
阅读优秀手册中的更多内容.


Joe*_*ams 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;
Run Code Online (Sandbox Code Playgroud)

通过定义"表格" on在顶部,我能够避免重复表达式things(OLD)things(NEW).

当然,我们可能会消除UNION ALL使用a 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.*;
Run Code Online (Sandbox Code Playgroud)

未经测试.如果有效,请告诉我.

它几乎就是你已经拥有的,除了:

  • 这可以很容易地扩展到更新多行.在使用子查询表达式的版本中,如果子查询已更改为生成多行,则查询将失败.

  • global.prospect在子查询中没有别名,所以它更容易阅读.由于这使用了一个FROM子句,如果您不小心引用了正在更新的表,您将收到错误.

  • 在您的版本中,每个项目都会遇到子查询表达式.虽然PostgreSQL应该对此进行优化并且仅对表达式进行一次计算,但如果您不小心引用了列psp或添加了volatile表达式,则此优化将会消失.