Como fazer uma atualização + participar do PostgreSQL?


508

Basicamente, eu quero fazer isso:

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

Tenho certeza de que funcionaria no MySQL (meu histórico), mas não parece funcionar no postgres. O erro que recebo é:

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

Certamente, há uma maneira fácil de fazer isso, mas não consigo encontrar a sintaxe adequada. Então, como eu escreveria isso no PostgreSQL?


5
A sintaxe do Postgres é diferente: postgresql.org/docs/8.1/static/sql-update.html
Marc B:

4
vehicle_vehicle, shipments_shipment? Isso é uma convenção de nomenclatura mesa interessante
CodeAndCats

3
@CodeAndCats Haha ... parece engraçado, não é? Eu acho que estava usando o Django na época, e as tabelas são agrupadas por recurso. Portanto, haveria uma vehicles_*tabela de exibição e algumas shipments_*tabelas.
MPEN

Respostas:


776

A sintaxe UPDATE é:

[WITH [RECURSIVE] with_query [, ...]]
Tabela UPDATE [ONLY] [[AS] alias]
    SET {coluna = {expressão | PADRÃO} |
          (coluna [, ...]) = ({expressão | PADRÃO} [, ...])} [, ...]
    [FROM from_list]
    [ONDE condição | ONDE ATUAL DO cursor_name]
    [VOLTANDO * | expressão_da_ saída [[AS] nome_da_ saída] [, ...]]

No seu caso, acho que você quer isso:

UPDATE vehicles_vehicle AS v 
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id 

Se a atualização se basear em uma lista inteira de junções de tabelas, elas devem estar na seção UPDATE ou FROM?
#

11
@ ted.strauss: O FROM pode conter uma lista de tabelas.
precisa

140

Deixe-me explicar um pouco mais pelo meu exemplo.

Tarefa: informações corretas, onde os alunos (alunos prestes a abandonar o ensino médio) enviaram solicitações para a universidade mais cedo do que obtiveram certificados escolares (sim, obtiveram certificados mais cedo do que foram emitidos (por data especificada). aumente a data de envio do aplicativo para se adequar à data de emissão do certificado.

Portanto. próxima instrução semelhante ao 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;

Torna-se semelhante ao PostgreSQL de tal maneira

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

Como você pode ver, JOINa ONcláusula da subconsulta original se tornou uma das WHEREcondições, combinadas ANDcom outras, que foram movidas da subconsulta sem alterações. E não há mais necessidade de JOINtabela com ela mesma (como era na subconsulta).


16
Como você se juntaria a uma terceira mesa?
Growler 03/02

19
Você apenas JOINcomo sempre na FROMlista:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek 4/17/17

@ Envek - Você não pode usar o JOIN, infelizmente, acabei de verificar. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, você não pode usar JOIN no próprio UPDATE, mas pode usá-lo na from_listcláusula UPDATE (que é a extensão do SQL do PostgreSQL). Além disso, consulte as notas sobre a junção de advertências nas tabelas no link que você forneceu.
Envek

@ Envek - Ah, obrigado pelo esclarecimento, eu perdi isso.
Adrian Smith

130

A resposta de Mark Byers é a ideal nessa situação. Embora em situações mais complexas, você possa pegar a consulta de seleção que retorna rowids e valores calculados e anexá-la à consulta de atualização como esta:

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

Essa abordagem permite desenvolver e testar sua consulta de seleção e, em duas etapas, convertê-la na consulta de atualização.

Portanto, no seu caso, a consulta do resultado será:

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

Observe que os aliases das colunas são obrigatórios, caso contrário, o PostgreSQL se queixará da ambiguidade dos nomes das colunas.


1
Eu realmente gosto deste porque estou sempre um pouco nervoso ao tirar meu "select" do topo e substituí-lo por uma "atualização", especialmente com várias junções. Isso reduz o número de despejos de SQL que eu deveria fazer antes das atualizações em massa. :)
dannysauer

5
Não sei por que, mas a versão CTE desta consulta é meio caminho mais rápido que o "simples juntar-se" soluções acima
paul.ago

A outra vantagem desta solução é a capacidade de ingressar em mais de duas tabelas para chegar ao seu valor final calculado usando várias associações na instrução with / select.
Alex Muro

1
Isso é incrível. Eu tinha o meu select criado e, como @dannysauer, fiquei com medo da conversão. Isso simplesmente faz isso por mim. Perfeito!
Frostymarvelous

1
Seu primeiro exemplo de SQL possui um erro de sintaxe. "update t1" não pode usar o alias da subconsulta t, ele precisa usar o nome da tabela: "update table1". Você faz isso corretamente no seu segundo exemplo.
Eric

80

Para aqueles que realmente querem fazer um, JOINvocê também pode usar:

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;

Você pode usar o a_alias na SETseção à direita do sinal de igual, se necessário. Os campos à esquerda do sinal de igual não requerem uma referência de tabela, pois são considerados da tabela "a" original.


4
Considerando que esta é a primeira resposta com uma junção real (e não dentro de uma com subconsulta), essa deve ser a resposta realmente aceita. Essa ou essa pergunta deve ser renomeada para evitar confusão se o postgresql suporta junções na atualização ou não.
colar

Funciona. Mas se sente como um hack
M. Habib

Deve-se observar que, de acordo com a documentação ( postgresql.org/docs/11/sql-update.html ), a listagem da tabela de destino na cláusula from fará com que a tabela de destino seja auto-associada. Com menos confiança, também me parece que essa é uma união cruzada, que pode ter resultados não intencionais e / ou implicações no desempenho.
Ben Collins

14

Para aqueles que desejam fazer um JOIN que atualize APENAS as linhas que o seu ingresso retorna:

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;

Isso foi mencionado acima, mas apenas através de um comentário. Como é fundamental obter o resultado correto, postando NOVA resposta que funciona


7

Aqui vamos nós:

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

Simples como eu poderia fazer. Obrigado rapazes!

Também pode fazer isso:

-- 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;

Mas então você tem a tabela de veículos lá duas vezes, e você só pode usar o alias uma vez e não pode usar o alias na parte "definida".


@littlegreen Você tem certeza disso? O não o joinrestringe?
MPEN

4
@pen Eu posso confirmar que ele atualiza todos os registros para um valor. não faz o que você esperaria.
Adam Bell

1

Aqui está um SQL simples que atualiza Mid_Name na tabela Name3 usando o campo Middle_Name de Name:

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


0

Nome da primeira tabela: tbl_table1 (tab1). Nome da segunda tabela: tbl_table2 (tab2).

Defina a coluna ac_status do tbl_table1 como "INATIVO"

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';
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.