Руководство по использованию предложения WITH в SQL - PullRequest
14 голосов
/ 04 января 2012

Я понимаю, как использовать предложение 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 ]

20 голосов
/ 04 января 2012

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


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

10 голосов
/ 04 января 2012

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.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 или добавляете переменное выражение.

...