¿Cómo hacer una actualización + unirse en PostgreSQL?

508

Básicamente, quiero hacer esto:

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

Estoy bastante seguro de que funcionaría en MySQL (mi fondo), pero no parece funcionar en postgres. El error que obtengo es:

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

Seguramente hay una manera fácil de hacer esto, pero no puedo encontrar la sintaxis adecuada. Entonces, ¿cómo escribiría esto en PostgreSQL?

mpen
fuente
55
La sintaxis de Postgres es diferente: postgresql.org/docs/8.1/static/sql-update.html
Marc B el
44
vehículos_vehículos, envíos_envío? Esa es una convención de nombres de tablas interesante
CodeAndCats
3
@CodeAndCats Jaja ... parece gracioso, ¿no? Creo que estaba usando Django en ese momento, y las tablas están agrupadas por función. Entonces habría habido una vista de vehicles_*tablas y algunas shipments_*tablas.
mpen

Respuestas:

777

La sintaxis de ACTUALIZACIÓN es:

[CON [RECURSIVO] with_query [, ...]]
ACTUALIZAR [SOLO] tabla [[AS] alias]
    SET {columna = {expresión | PREDETERMINADO} |
          (columna [, ...]) = ({expresión | POR DEFECTO} [, ...])} [, ...]
    [DE from_list]
    [DONDE condición | DONDE CORRIENTE DE cursor_name]
    [REGRESO * | expresión_salida [[AS] nombre_salida] [, ...]]

En tu caso, creo que quieres esto:

UPDATE vehicles_vehicle AS v 
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id 
Mark Byers
fuente
Si la actualización se basa en una lista completa de combinaciones de tablas, ¿deberían estar en la sección ACTUALIZAR o en la sección DESDE?
ted.strauss
11
@ ted.strauss: el FROM puede contener una lista de tablas.
Mark Byers
140

Déjame explicarte un poco más con mi ejemplo.

Tarea: información correcta, donde los abiturients (estudiantes a punto de abandonar la escuela secundaria) han presentado solicitudes a la universidad antes de que obtuvieran certificados escolares (sí, obtuvieron certificados antes de que fueran emitidos (por la fecha del certificado especificada). aumente la fecha de envío de la solicitud para que se ajuste a la fecha de emisión del certificado.

Así. siguiente declaración similar a 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;

Se convierte en PostgreSQL de tal manera

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 puede ver, JOINla ONcláusula de la subconsulta original se ha convertido en una de las WHEREcondiciones, que se conjuga ANDcon otras, que se han movido de la subconsulta sin cambios. Y ya no hay necesidad de presentar una JOINtabla consigo mismo (como estaba en la subconsulta).

Envek
fuente
16
¿Cómo te unirías a una tercera mesa?
Growler
19
Simplemente JOINcomo siempre en la FROMlista:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek
@Envek: no puedes usar JOIN allí, por desgracia, acabo de comprobarlo. postgresql.org/docs/10/static/sql-update.html
Adrian Smith
3
@AdrianSmith, no puede usar JOIN en UPDATE, pero puede usarlo en la from_listcláusula UPDATE (que es la extensión de SQL de PostgreSQL). Además, vea las notas sobre las advertencias de unión de tablas en el enlace que proporcionó.
Envek
@Envek - Ah, gracias por la aclaración, me perdí eso.
Adrian Smith el
130

La respuesta de Mark Byers es la óptima en esta situación. Aunque en situaciones más complejas, puede tomar la consulta de selección que devuelve los ID de fila y los valores calculados y adjuntarla a la consulta de actualización de esta manera:

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

Este enfoque le permite desarrollar y probar su consulta de selección y en dos pasos convertirla a la consulta de actualización.

Entonces, en su caso, la consulta de resultados 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

Tenga en cuenta que los alias de columna son obligatorios; de lo contrario, PostgreSQL se quejará de la ambigüedad de los nombres de columna.

Alvin
fuente
1
Realmente me gusta este porque siempre estoy un poco nervioso al quitar mi "selección" de la parte superior y reemplazarla con una "actualización", especialmente con múltiples combinaciones. Esto reduce la cantidad de volcados de SQL que debería hacer antes de las actualizaciones masivas. :)
dannysauer
55
No estoy seguro de por qué, pero la versión CTE de esta consulta es mucho más rápida que las soluciones de "combinación simple" anteriores
paul.ago
La otra ventaja de esta solución es la capacidad de unirse desde más de dos tablas para llegar a su valor final calculado mediante el uso de múltiples combinaciones en la instrucción with / select.
Alex Muro
1
Esto es asombroso Tenía mi selección diseñada y, como @dannysauer, tenía miedo de la conversión. Esto simplemente lo hace por mí. ¡Perfecto!
frostymarvelous
1
Su primer ejemplo de SQL tiene un error de sintaxis. "actualizar t1" no puede usar el alias de la subconsulta t, necesita usar el nombre de la tabla: "actualizar tabla1". Haces esto correctamente en tu segundo ejemplo.
EricS
80

Para aquellos que realmente quieran hacer algo JOIN, también pueden 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;

Puede usar a_alias en la SETsección a la derecha del signo igual si es necesario. Los campos a la izquierda del signo igual no requieren una referencia de tabla, ya que se consideran de la tabla "a" original.

Engy rápido
fuente
44
Teniendo en cuenta que esta es la primera respuesta con una unión real (y no dentro de una con subconsulta), esta debería ser la respuesta real aceptada. O eso o esta pregunta debe cambiarse de nombre para evitar confusiones sobre si postgresql admite combinaciones en la actualización o no.
collar
Funciona. Pero se siente como un hack
M. Habib
Cabe señalar que de acuerdo con la documentación ( postgresql.org/docs/11/sql-update.html ), la inclusión de la tabla de destino en la cláusula from hará que la tabla de destino se una a sí misma. Con menos confianza, también me parece que se trata de una unión cruzada, que puede tener resultados no deseados y / o implicaciones de rendimiento.
Ben Collins
14

Para aquellos que quieran hacer una UNIÓN que actualice SOLO las filas que su unión devuelve use:

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;

Esto se mencionó anteriormente, pero solo a través de un comentario ... Dado que es fundamental obtener el resultado correcto publicando NUEVA respuesta que funcione

Nate Smith
fuente
7

Aquí vamos:

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

Tan simple como pude hacerlo. ¡Gracias chicos!

También puede hacer esto:

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

Pero luego tienes la tabla del vehículo allí dos veces, y solo puedes alias una vez, y no puedes usar el alias en la parte "set".

mpen
fuente
@littlegreen ¿Estás seguro de eso? ¿No lo joinrestringe?
mpen
44
@mpen Puedo confirmar que actualiza todos los registros a un valor. no hace lo que esperarías.
Adam Bell
1

Aquí hay un SQL simple que actualiza Mid_Name en la tabla Name3 usando el campo Middle_Name de Name:

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

El siguiente enlace tiene un ejemplo que resuelve y ayuda a entender mejor cómo usar updatey joincon postgres.

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

Ver: http://www.postgresqltutorial.com/postgresql-update-join/

Alessandro
fuente
0

Nombre de la primera tabla: tbl_table1 (tab1). Nombre de la segunda tabla: tbl_table2 (tab2).

Establezca la columna ac_status de tbl_table1 en "INACTIVO"

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
fuente