Обновление PostgreSQL, если условие истинно, но возвращает идентификатор на основе другого условия

#sql #postgresql #sql-update

#sql #postgresql #sql-обновление

Вопрос:

Прошу прощения, если я что-то упустил, но я немного поискал и не могу найти очевидный ответ. У меня есть система, в которой несколько рабочих используют таблицу postgres, и этим рабочим необходимо обновить некоторые строки на основе идентификатора, чтобы сказать, что какая-то операция завершена. Таким образом, очень вероятно, что два работника попытаются обновить одни и те же строки.

Упрощенная версия моей таблицы выглядит следующим образом: три поля — int, bool и timestamp соответственно:

  ---- ------- -------------------------- 
| id | done  |      done_timestamp      |
 ---- ------- -------------------------- 
|  1 | false | null                     |
|  2 | false | null                     |
|  3 | true  | 2020-11-24T21:23:04.008Z |
 ---- ------- -------------------------- 
 

У меня есть функция обновления, которую я хочу использовать для обновления всех переданных строк, но не тех, которые уже помечены как выполненные. Это выглядит так:

 UPDATE operations AS o
SET done = TRUE,
done_timestamp = o2.done_timestamp
FROM (VALUES (1, NOW()), (2, NOW()), (3, NOW())) AS o2(id, done_timestamp)
WHERE o2.id = o.id AND NOT o.done;
 

Пока все хорошо. Я обновляю 2 из трех строк, и я не обновляю 3-ю, потому что что-то уже это сделало, и я не хочу обновлять там временную метку. Вот что я получаю в своей таблице:

  ---- ------ -------------------------- 
| id | done |      done_timestamp      |
 ---- ------ -------------------------- 
|  1 | true | 2020-11-24T21:38:29.871Z |
|  2 | true | 2020-11-24T21:38:29.871Z |
|  3 | true | 2020-11-24T21:23:04.008Z |
 ---- ------ -------------------------- 
 

Последняя часть головоломки заключается в том, что я хочу вернуть все обновленные идентификаторы, чтобы я мог выполнить некоторую проверку ошибок. Моей первой мыслью было добавить RETURNING оператор, но, делая это, я возвращаю только идентификаторы, обновленные этим запросом, а не все строки, которые выполнены. Запрос выглядит следующим образом:

 UPDATE operations AS o
SET done = TRUE,
done_timestamp = o2.done_timestamp
FROM (VALUES (1, NOW()), (2, NOW()), (3, NOW())) AS o2(id, done_timestamp)
WHERE o2.id = o.id AND NOT o.done
RETURNING o.id;
 

Есть ли у кого-нибудь хороший способ вернуть все идентификаторы строк, которые соответствуют идентификаторам, которые я передаю, и которые также были помечены как выполненные, а не только те, которые были специально обновлены в моем запросе на обновление?

Заранее спасибо, F

Комментарии:

1. psql является ли Postgres клиентом командной строки, это действительно не имеет ничего общего с вопросом. Вы хотите знать «Обновление Postgresql, если условие …». Пожалуйста, измените заголовок, чтобы сделать этот вопрос более понятным.

2. Ах, извинения. Я новичок в Postgres и подумал, что psql это просто сокращение, чтобы сделать его менее подробным. Обновлено.

3. Спасибо. Postgres будет «официальной» сокращенной версией имени.

Ответ №1:

Насколько я могу судить, вам нужно будет обмануть ваш сервер, чтобы сделать это. Заставьте его думать, что обновление было, когда его не было, переместив логическую проверку в оператор CASE; если done = TRUE уже, установите для него ту же временную метку, которая уже существовала.

Примечание: это приводит к ненужным ОБНОВЛЕНИЯМ таблицы и приведет к замедлению запроса, но не похоже, что Postgres поддерживает какое-либо СЛИЯНИЕ, которое вернет вам идентификаторы, как это было бы в некоторых других БД.

 UPDATE operations AS o
SET done_ts = case when done = false then o2.done_ts else o.done_ts end,
    done = TRUE
FROM (VALUES (1, NOW()), (2, NOW()), (3, NOW())) AS o2(id, done_ts)
WHERE o2.id = o.id 
RETURNING o.id;
 

Комментарии:

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

Ответ №2:

Не проверено на ваших данных, но возможное решение с использованием [CTE] s(https://www.postgresql.org/docs/current/sql-select.html#SQL-WITH ):

 WITH val(id, done_timestamp) AS 
        (VALUES (1, NOW()), (2, NOW()), (3, NOW())) , 
updated AS 
    (UPDATE operations AS o
        SET done = TRUE, done_timestamp = val.done_timestamp
    WHERE val.id = o.id AND NOT o.done) 
SELECT id FROM val;
 

Комментарии:

1. Поправьте меня, если я ошибаюсь, но разве это решение не будет также возвращать идентификаторы, даже если их нет в таблице?

2. Затем в SELECT соединении operations с любой подходящей логикой фильтра.