Возврат значений столбцов pre-UPDATE с использованием только SQL - версия PostgreSQL - PullRequest
40 голосов
/ 28 октября 2011

У меня есть связанный вопрос , но это еще одна часть моей загадки.

Я хотел бы получить СТАРОЕ ЗНАЧЕНИЕ столбца из строки, которая была ОБНОВЛЕНА - БЕЗ использования триггеров (ни хранимых процедур, ни каких-либо дополнительных, не-SQL / -запрошенных объектов).

У меня такой запрос:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
    WHERE trans_nbr IN (
                        SELECT trans_nbr
                          FROM my_table
                         GROUP BY trans_nbr
                        HAVING COUNT(trans_nbr) > 1
                         LIMIT our_limit_to_have_single_process_grab
                       )
RETURNING row_id;

Если бы я мог сделать «FOR UPDATE ON my_table» в конце подзапроса, это было бы божественным (и исправил бы мой другой вопрос / проблему). Но это не сработает: не может иметь этого И «GROUP BY» (что необходимо для определения СЧЕТА trans_nbr's). Тогда я мог бы просто взять эти trans_nbr и выполнить сначала запрос, чтобы получить (скоро, чтобы быть) прежние значения processing_by.

Я пытался сделать так:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
     FROM my_table old_my_table
     JOIN (
             SELECT trans_nbr
               FROM my_table
           GROUP BY trans_nbr
             HAVING COUNT(trans_nbr) > 1
              LIMIT our_limit_to_have_single_process_grab
          ) sub_my_table
       ON old_my_table.trans_nbr = sub_my_table.trans_nbr
    WHERE     my_table.trans_nbr = sub_my_table.trans_nbr
      AND my_table.processing_by = old_my_table.processing_by
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by

Но это не может сработать; old_my_table не видно вне объединения; пункт RETURNING слеп к нему.

Я давно потерял счет всех своих попыток; Я изучал это буквально часами.

Если бы я мог просто найти пуленепробиваемый способ блокировки строк в моем подзапросе - и ТОЛЬКО этих строк, и КОГДА подзапрос произойдет - все проблемы параллелизма, которые я пытаюсь избежать, исчезли бы ...


ОБНОВЛЕНИЕ: [WIPES EGG OFF OFF FACE] Хорошо, у меня была опечатка в неуниверсальном коде вышеупомянутого, который я написал "не работает"; это делает ... благодаря Эрвину Брандштеттеру , ниже, который заявил, что это будет, я сделал это заново (после ночного сна, освеженных глаз и банана для баста). Поскольку мне потребовалось так много времени / усилий, чтобы найти такое решение, возможно, мое смущение того стоит? По крайней мере, это так для потомков ...:>

То, что у меня сейчас есть (это работает), выглядит так:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
     FROM my_table AS old_my_table
    WHERE trans_nbr IN (
                          SELECT trans_nbr
                            FROM my_table
                        GROUP BY trans_nbr
                          HAVING COUNT(*) > 1
                           LIMIT our_limit_to_have_single_process_grab
                       )
      AND my_table.row_id = old_my_table.row_id
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by AS old_processing_by

COUNT (*) соответствует предложению Flimzy в комментарии к моему другому (связанному выше) вопросу. (Я был более конкретным, чем необходимо. [В данном случае.))

Пожалуйста, смотрите мой другой вопрос для правильной реализации параллелизма и даже неблокирующей версии; Этот запрос просто показывает, как получить старые и новые значения из обновления, игнорируя биты неправильного / неправильного параллелизма.

Ответы [ 4 ]

63 голосов
/ 28 октября 2011

Проблема

В руководстве объясняется :

Необязательное предложение RETURNING заставляет UPDATE вычислять и возвращать значения на основе каждогострока действительно обновлена.Любое выражение, использующее столбцы таблицы и / или столбцы других таблиц, упомянутых в FROM, может быть вычислено. новые (после обновления) значения столбцов таблицы используются .Синтаксис списка RETURNING идентичен синтаксису списка вывода SELECT.

.Нет доступа к старой строке в предложении RETURNING.Вы можете сделать это в триггере или с отдельным SELECT перед UPDATE, завернутым в транзакцию как комментарии @Flimzy и @wildplasser, или обернутым в CTE как сообщение @MattDiPasquale.*

Решение

Однако то, что вы пытаетесь достичь , прекрасно работает , если вы присоединитесь к другому экземпляру таблицы в предложении FROM:

UPDATE tbl x
SET    tbl_id = 23
     , name = 'New Guy'
FROM   tbl y                -- using the FROM clause
WHERE  x.tbl_id = y.tbl_id  -- must be UNIQUE NOT NULL
AND    x.tbl_id = 3
RETURNING y.tbl_id AS old_id, y.name AS old_name
        , x.tbl_id          , x.name;

Возвращает:

 old_id | old_name | tbl_id |  name
--------+----------+--------+---------
  3     | Old Guy  | 23     | New Guy

SQL Fiddle.

Я проверял это с версиями PostgreSQL от 8.4 до 9.6.

Это отличается для INSERT:

Работа с одновременной записьюload

Существует несколько способов избежать состояния гонки с одновременными операциями записи.Простой, медленный и надежный (но дорогой) метод - запустить транзакцию с уровнем изоляции SERIALIZABLE.

BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ..;
COMMIT;

Но это, вероятно, излишне.И вам нужно быть готовым повторить операцию, если вы получите ошибку сериализации.
Проще и быстрее (и так же надежно при одновременной загрузке записи) является явная блокировка строки one длябыть обновлено:

UPDATE tbl x
SET    tbl_id = 24
     , name = 'New Gal'
FROM  (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 <b>FOR UPDATE</b>) y 
WHERE  x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name, x.tbl_id, x.name;

Дополнительные пояснения, примеры и ссылки по этому связанному вопросу:

8 голосов
/ 29 марта 2014

Вы можете использовать подзапрос SELECT.

Пример: Обновить адрес электронной почты пользователя RETURNING старое значение.

  1. RETURNING Подзапрос

    UPDATE users SET email = 'new@gmail.com' WHERE id = 1
    RETURNING (SELECT email FROM users WHERE id = 1);
    
  2. PostgreSQL с запросом (общие табличные выражения)

    WITH u AS (
        SELECT email FROM users WHERE id = 1
    )
    UPDATE users SET email = 'new@gmail.com' WHERE id = 1
    RETURNING (SELECT email FROM u);
    

    Это работало несколько раз в моей локальной базе данных без сбоев, но я не уверен, гарантируется ли последовательное выполнение SELECT в WITH до UPDATE, так как выполняются под-операторы в WITH одновременно друг с другом и с основным запросом. "

6 голосов
/ 30 марта 2014

Вариант CTE как , предложенный @ MattDiPasquale , должен работать тоже.
С удобными средствами CTE я был бы более точным, хотя:

WITH sel AS (
   SELECT tbl_id, name FROM tbl WHERE tbl_id = 3  -- assuming unique tbl_id
   )
, upd AS (
   UPDATE tbl SET name = 'New Guy' WHERE tbl_id = 3
   RETURNING tbl_id, name
   )
SELECT s.tbl_id AS old_id, s.name As old_name
     , u.tbl_id, u.name
FROM   sel s, upd u;

Без тестированияЯ утверждаю, что это работает: SELECT и UPDATE видят тот же снимок базы данных.SELECT обязательно возвращает старые значения (даже если вы ставите CTE после CTE с UPDATE), в то время как UPDATE возвращает новые значения по определению.Вуаля.

Но это будет медленнее, чем мой первый ответ.

1 голос
/ 16 июля 2018

, столкнувшись с этой дилеммой, я добавил столбцы нежелательной почты в таблицу, а затем скопировал старые значения в столбцы нежелательной почты (которые затем возвращаю), когда обновляю запись. это немного раздувает таблицу, но избавляет от необходимости объединений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...