MERGE, UPDATE y DELETE
Parquet suelto no tiene DML de tabla. Delta reescribe los data files que contienen las filas afectadas y commitea un snapshot nuevo. UPDATE, DELETE y MERGE son operaciones caras si el predicado toca muchos ficheros; no son un UPDATE de fila en un B-tree.
Documentación: deletes, updates y merges, Change Data Feed.
DELETE y UPDATE
Clientes actuales en /data/customers:
DELETE FROM delta.`/data/customers`
WHERE status = 'closed' AND country = 'ES';
UPDATE delta.`/data/customers`
SET plan = 'pro'
WHERE customer_id = 9001;from delta.tables import DeltaTable
from pyspark.sql.functions import col, lit
clientes = DeltaTable.forPath(spark, "/data/customers")
clientes.delete("status = 'closed' AND country = 'ES'")
clientes.update(
condition=col("customer_id") == 9001,
set={"plan": lit("pro")},
)Sin predicado, DELETE vacía la tabla (nuevo snapshot vacío; el historial sigue). Un WHERE que no acota particiones obliga a escanear —y potencialmente reescribir— muchos ficheros.
MERGE: target, source, matching
MERGE compara un target (tabla Delta) con un source (tabla, vista o DataFrame) mediante una condición. Luego:
WHEN MATCHED— la fila existe en ambos:UPDATEoDELETE;WHEN NOT MATCHED— solo en el source:INSERT;WHEN NOT MATCHED BY SOURCE— solo en el target:UPDATEoDELETE(cláusula extendida; úsala con predicado extra o reescribes el target entero).
Upsert de cambios de clientes:
MERGE INTO delta.`/data/customers` AS target
USING customer_changes AS source
ON target.customer_id = source.customer_id
WHEN MATCHED AND source.op = 'delete' THEN DELETE
WHEN MATCHED THEN UPDATE SET
email = source.email,
plan = source.plan,
status = source.status,
updated_at = source.updated_at
WHEN NOT MATCHED AND source.op <> 'delete' THEN INSERT (
customer_id, email, plan, status, updated_at
) VALUES (
source.customer_id, source.email, source.plan, source.status, source.updated_at
);from delta.tables import DeltaTable
cambios = spark.table("customer_changes")
dest = DeltaTable.forPath(spark, "/data/customers")
dest.alias("target").merge(
cambios.alias("source"),
"target.customer_id = source.customer_id",
).whenMatchedDelete(
condition="source.op = 'delete'"
).whenMatchedUpdate(
set={
"email": "source.email",
"plan": "source.plan",
"status": "source.status",
"updated_at": "source.updated_at",
}
).whenNotMatchedInsert(
condition="source.op <> 'delete'",
values={
"customer_id": "source.customer_id",
"email": "source.email",
"plan": "source.plan",
"status": "source.status",
"updated_at": "source.updated_at",
},
).execute()updateAll() / insertAll() / UPDATE SET * exigen que el source tenga las columnas del target. Las columnas extra del source se ignoran salvo que actives evolución de esquema en ese merge.
MERGE no es magia
- Coste. Delta localiza ficheros candidatos, reescribe los que contienen matches y commitea Add+Remove. Un merge diario sobre toda la dimensión es un full rewrite encubierto.
- La condición importa. Si el target está particionado por
country, incluyetarget.country = source.countryy, si el job es por país,target.country = 'ES'. Así reduces el espacio de búsqueda y los conflictos con otros writers (capítulo 3). - Clave.
customer_id(o la clave de negocio) debe identificar una fila target. Si el source trae dos filas para el mismocustomer_id, el merge falla o el resultado es ambiguo: deduplica el source antes. - No es “siempre rápido”. Ni sustituye un índice OLTP. Mídelo; acota particiones; no merges contra la tabla de eventos crudos si puedes aplicar el cambio en una tabla estrecha.
Sirve para upserts, CDC aplicado a una tabla actual, SCD tipo 1 (pisar atributos) o pasos de deduplicación. No convierte este capítulo en un manual de modelado dimensional.
Change Data Feed no es MERGE
MERGE aplica un source sobre un target. Change Data Feed (CDF) registra cambios de fila entre versiones (insert, update_preimage, update_postimage, delete) para que otro job los lea.
No está activo en todas las tablas. Hay que habilitarlo:
ALTER TABLE events SET TBLPROPERTIES (delta.enableChangeDataFeed = true);En tablas nuevas, TBLPROPERTIES en el CREATE. Los registros viven (cuando hacen falta ficheros extra) bajo _change_data y siguen la retención de la tabla: un VACUUM también se los lleva. CDF no es un sistema de auditoría permanente salvo que copies el feed a otra tabla o a un log externo.
Lectura batch:
SELECT *
FROM table_changes_by_path('/data/customers', 18, 22);Streaming: .option("readChangeFeed", "true") (capítulo 7). Columnas extra: _change_type, _commit_version, _commit_timestamp.
Si lees CDF desde antes de habilitarlo, falla: no hay eventos de cambio que inventar.
Siguiente: Time travel.
