Як зробити оновлення + приєднатись до 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?


5
Синтаксис Postgres відрізняється: postgresql.org/docs/8.1/static/sql-update.html
Марк Б

4
транспортні засоби_товари, перевезення_перевезення? Ось цікава таблиця конвенцій іменування
CodeAndCats

3
@CodeAndCats Ха-ха ... це виглядає смішно чи не так? Я думаю, що в той час я використовував Django, і таблиці згруповані за ознаками. Так були б vehicles_*таблиці перегляду та кілька shipments_*таблиць.
mpen

Відповіді:


776

Синтаксис UPDATE є:

[З [RECURSIVE] з_запитом [, ...]]
ОНОВЛЕННЯ [ТОЛЬКО] таблиці [[AS] псевдонім]
    SET {колонка = {вираз | ЗАВДАННЯ} |
          (стовпець [, ...]) = ({вираз | DEFAULT} [, ...])} [, ...]
    [FROM 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таблиці з самим собою (як це було в підзапиті).


16
Як би ви приєдналися до третьої таблиці?
Гроулер

19
Ви просто JOINце, як зазвичай, у FROMсписку:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Енвек

@Envek - Ви не можете використовувати приєднатися там, на жаль, я просто перевірив. postgresql.org/docs/10/static/sql-update.html
Адріан Сміт

3
@AdrianSmith, ви не можете використовувати JOIN в самому UPDATE, але можете використовувати його в from_listпункті UPDATE (це розширення PostgreSQL для SQL). Також дивіться примітки про приєднання таблиць застережень за посиланням, яке ви надали.
Енвек

@Envek - Ах, дякую за роз’яснення, я пропустив це.
Адріан Сміт

130

Відповідь Марка Байєрса - оптимальна в цій ситуації. Хоча у складніших ситуаціях ви можете взяти запит вибору, який повертає рядки та обчислені значення та додайте його до запиту оновлення таким чином:

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 буде скаржитися на неоднозначність імен стовпців.


1
Мені це дуже подобається, тому що я завжди нервую, коли знімаю "вибір" зверху і замінюю його "оновленням", особливо з кількома приєднаннями. Це зменшує кількість відвалів SQL, які мені доведеться робити перед масовими оновленнями. :)
dannysauer

5
Не впевнений, чому, але версія CTE цього запиту набагато швидша, ніж рішення "звичайного приєднання" вище
paul.ago

Іншою перевагою цього рішення є можливість об'єднання з більш ніж двох таблиць, щоб дістати до остаточного обчисленого значення, використовуючи кілька об'єднань у операторі з / select.
Алекс Муро

1
Це круто. У мене був обраний мій вибір і мені подобається @dannysauer, я злякався конверсії. Це просто робить це для мене. Ідеально!
frostymarvelous

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
Враховуючи, що це перша відповідь з фактичним приєднанням (а не всередині a з підзапитом), це має бути реально прийнятою відповіддю. Або це, або це питання слід перейменувати, щоб уникнути плутанини, чи підтримує postgresql приєднання до оновлення чи ні.
намисто

Це працює. Але відчуває себе злом
М. Хабіб

Слід зазначити, що відповідно до документації ( postgresql.org/docs/11/sql-update.html ), перерахування цільової таблиці в пункті з клаузу може призвести до самостійного приєднання цільової таблиці. Менш впевнено мені також здається, що це перехресне самозаєднання, яке може мати непередбачувані результати та / або наслідки для продуктивності.
Бен Коллінз

14

Для тих, хто хоче приєднатись до СПОЛУЧЕННЯ, який оновлює ТОЛЬКІ рядки, які використовує ваш приєднання:

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;

Але тоді ви двічі занесли таблицю транспортного засобу, і вам дозволяється псевдонім його лише один раз, і ви не можете використовувати псевдонім у розділі "встановити".


@littlegreen Ви впевнені в цьому? Чи не joinобмежує це?
mpen

4
@mpen Я можу підтвердити, що він оновлює всі записи до одного значення. це не робить того, що ви очікували.
Адам Белл

1

Ось простий SQL, який оновлює Mid_Name у таблиці Name3, використовуючи поле Middle_Name з Імені:

update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;


0

Назва першої таблиці: tbl_table1 (tab1). Назва другої таблиці: tbl_table2 (tab2).

Встановіть стовпець ac_status tbl_table1 на "INACTIVE"

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';
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.