Как сделать обновление + присоединиться к PostgreSQL?

508

В основном, я хочу сделать это:

update vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id=s.id 
set v.price=s.price_per_vehicle;

Я почти уверен, что это будет работать в MySQL (мой опыт), но, похоже, это не работает в postgres. Я получаю ошибку:

ERROR:  syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
                                  ^

Конечно, есть простой способ сделать это, но я не могу найти правильный синтаксис. Итак, как бы я написал это в PostgreSQL?

mpen
источник
5
Синтаксис Postgres отличается: postgresql.org/docs/8.1/static/sql-update.html
Марк B,
4
транспортные средства, грузоперевозки, доставка? Это интересное соглашение об именах
таблиц
3
@CodeAndCats Ха-ха ... это выглядит смешно, не так ли? Я думаю, что я использовал Django в то время, и таблицы сгруппированы по функциям. Таким образом, было бы представление vehicles_*таблицы и несколько shipments_*таблиц.
mpen

Ответы:

777

Синтаксис UPDATE является:

[WITH [RECURSIVE] with_query [, ...]]
ОБНОВЛЕНИЕ [ТОЛЬКО] таблица [[AS] псевдоним]
    SET {колонка = {выражение | ПО УМОЛЧАНИЮ} |
          (столбец [, ...]) = ({выражение | ПО УМОЛЧАНИЮ} [, ...])} [, ...]
    [ОТ from_list]
    [ГДЕ состояние | ГДЕ ТОКА ИЗ КУРСА
    [ВОЗВРАЩЕНИЕ * | output_expression [[AS] output_name] [, ...]]

В вашем случае я думаю, что вы хотите этого:

UPDATE vehicles_vehicle AS v 
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id 
Марк Байерс
источник
Если обновление опирается на целый список объединений таблиц, должны ли они быть в разделе ОБНОВЛЕНИЕ или в разделе ОТ?
ted.strauss
11
@ ted.strauss: FROM может содержать список таблиц.
Марк Байерс
140

Позвольте мне объяснить немного больше на моем примере.

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

Таким образом. следующее MySQL-подобное утверждение:

UPDATE applications a
JOIN (
    SELECT ap.id, ab.certificate_issued_at
    FROM abiturients ab
    JOIN applications ap 
    ON ab.id = ap.abiturient_id 
    WHERE ap.documents_taken_at::date < ab.certificate_issued_at
) b
ON a.id = b.id
SET a.documents_taken_at = b.certificate_issued_at;

Таким образом, становится PostgreSQL-подобным

UPDATE applications a
SET documents_taken_at = b.certificate_issued_at         -- we can reference joined table here
FROM abiturients b                                       -- joined table
WHERE 
    a.abiturient_id = b.id AND                           -- JOIN ON clause
    a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE

Как вы можете видеть, оригинальный подзапрос JOIN«s ONположение стало одним из WHEREусловий, которое conjucted путем ANDс другими, которые были перемещены из подзапроса без каких - либо изменений. И больше нет необходимости JOINобъединяться с самим собой (как это было в подзапросе).

Envek
источник
16
Как бы вы присоединились к третьему столу?
Growler
19
Вы просто JOINкак обычно в FROMсписке:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek
@ Envek - Вы не можете использовать JOIN там, увы, я только что проверил. postgresql.org/docs/10/static/sql-update.html
Адриан Смит,
3
@AdrianSmith, вы не можете использовать JOIN в самом UPDATE, но можете использовать его в предложении UPDATE from_list(которое является расширением PostgreSQL для SQL). Кроме того, см. Примечания о присоединении к таблицам предостережения по предоставленной вами ссылке.
Envek
@Envek - Ах, спасибо за разъяснения, я пропустил это.
Адриан Смит
130

Ответ Марка Байерса является оптимальным в этой ситуации. Хотя в более сложных ситуациях вы можете взять запрос select, который возвращает значения rowid и вычисленные значения, и прикрепить его к запросу на обновление следующим образом:

with t as (
  -- Any generic query which returns rowid and corresponding calculated values
  select t1.id as rowid, f(t2, t2) as calculatedvalue
  from table1 as t1
  join table2 as t2 on t2.referenceid = t1.id
)
update table1
set value = t.calculatedvalue
from t
where id = t.rowid

Этот подход позволяет вам разработать и протестировать ваш запрос на выборку и в два шага преобразовать его в запрос на обновление.

Так что в вашем случае результат запроса будет:

with t as (
    select v.id as rowid, s.price_per_vehicle as calculatedvalue
    from vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id = s.id 
)
update vehicles_vehicle
set price = t.calculatedvalue
from t
where id = t.rowid

Обратите внимание, что псевдонимы столбцов являются обязательными, иначе PostgreSQL будет жаловаться на неоднозначность имен столбцов.

Alvin
источник
1
Мне очень нравится этот, потому что я всегда немного нервничаю, когда беру свой «выбор» сверху и заменяю его «обновлением», особенно с несколькими объединениями. Это уменьшает количество дампов SQL, которые я должен сделать перед массовыми обновлениями. :)
dannysauer
5
Не знаю почему, но версия этого запроса для CTE намного быстрее, чем описанные выше решения для простого соединения
paul.ago
Другим преимуществом этого решения является возможность объединения из более чем двух таблиц для получения окончательного вычисленного значения с помощью нескольких объединений в операторе with / select.
Алекс Муро
1
Это круто. Я выбрал свой выбор и, как @dannysauer, боялся преобразования. Это просто делает это для меня. Отлично!
морозное
1
Ваш первый пример SQL имеет синтаксическую ошибку. «update t1» не может использовать псевдоним из подзапроса t, ему нужно использовать имя таблицы: «update table1». Вы делаете это правильно во втором примере.
EricS
80

Для тех, кто действительно хочет сделать, JOINвы также можете использовать:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id;

При необходимости вы можете использовать a_alias в SETразделе справа от знака равенства. Поля слева от знака равенства не требуют ссылки на таблицу, так как они считаются исходной таблицей «а».

Быстрая Энги
источник
4
Учитывая, что это первый ответ с фактическим соединением (а не внутри подзапроса), это должен быть реальный принятый ответ. Либо этот, либо этот вопрос следует переименовать, чтобы избежать путаницы в том, поддерживает ли postgresql соединения в обновлении или нет.
ожерелье
Оно работает. Но похоже на взлом
М. Хабиб
Следует отметить, что согласно документации ( postgresql.org/docs/11/sql-update.html ), перечисление целевой таблицы в предложении from приведет к самостоятельному объединению целевой таблицы. Менее уверенно, мне также кажется, что это перекрестное самостоятельное объединение, которое может иметь непредвиденные результаты и / или последствия для производительности.
Бен Коллинз
14

Для тех, кто хочет создать JOIN, который обновляет ТОЛЬКО те строки, которые использует ваше соединение:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id
--the below line is critical for updating ONLY joined rows
AND a.pk_id = a_alias.pk_id;

Это было упомянуто выше, но только через комментарий. Так как важно получить правильный результат, публикуя НОВЫЙ ответ, который работает

Нейт Смит
источник
7

Вот так:

update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;

Просто, как я мог сделать это. Спасибо, парни!

Можно также сделать это:

-- Doesn't work apparently
update vehicles_vehicle 
set price=s.price_per_vehicle
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id;

Но затем у вас есть таблица транспортных средств там дважды, и вам разрешено использовать ее псевдоним только один раз, и вы не можете использовать псевдоним в части «set».

mpen
источник
@littlegreen Ты уверен в этом? Разве это не joinсдерживает?
mpen
4
@mpen Я могу подтвердить, что он обновляет все записи до одного значения. это не делает то, что вы ожидаете.
Адам Белл
1

Вот простой SQL, который обновляет Mid_Name в таблице Name3, используя поле Middle_Name из Name:

update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;
Юсуф
источник
0

Ссылка ниже содержит пример, который решает и помогает лучше понять, как использовать updateи joinс postgres.

UPDATE product
SET net_price = price - price * discount
FROM
product_segment
WHERE
product.segment_id = product_segment.id;

Смотрите: http://www.postgresqltutorial.com/postgresql-update-join/

Alessandro
источник
0

Имя первой таблицы: tbl_table1 (tab1). Имя второй таблицы: tbl_table2 (tab2).

Установите для столбца ac_status таблицы tbl_table1 значение «НЕАКТИВНО»

update common.tbl_table1 as tab1
set ac_status= 'INACTIVE' --tbl_table1's "ac_status"
from common.tbl_table2 as tab2
where tab1.ref_id= '1111111' 
and tab2.rel_type= 'CUSTOMER';
AritraDB
источник