Руководство по использованию предложения WITH в SQL

Я понимаю, как использовать WITH предложение для рекурсивных запросов (!!), но у меня возникли проблемы с пониманием его общего использования / мощности.

например, следующий запрос обновляет одну запись, идентификатор которой определяется с помощью подзапроса, возвращающего идентификатор первой записи по метке времени:

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 ответов


Если может быть одновременный доступ на запись к задействованным таблицам, условия гонки в вышеуказанных следующих запросах. Подумайте:


ваш пример можете используйте 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 или более поздней версии.
Читать больше в превосходном руководстве.


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;

определив "таблицы"o и n в верхней части я смог избежать повторения выражений things(OLD) и things(NEW).

конечно, мы могли бы исключить UNION ALL С помощью FULL JOIN, но я не смог это сделать в моем конкретном случае.


если Я правильно понимаю ваш запрос, он делает следующее:

  • найдите самую старую строку в global.перспектива, чей статус "новый" или "сброс".

  • отметьте его, добавив звездочку к его статусу

  • верните строку (включая нашу настройку в 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 или добавьте выражение volatile.