En SQL Server, para eliminar registros de una tabla siguiendo un criterio que tenga relación con otra tabla de la base de datos se puede establecer una join entre ambas tablas en la misma instrucción SQL de Delete.
Es algo muy parecido al método para hacer un update a partir de una join para actualizar registros en función de valores de otras tablas, pero en este caso, al tratarse de eliminar registros en lugar de actualizar, la sintaxis SQL cambia un poco.
Si queremos hacer un delete de registros utilizando una join lo único que hay que hacer es especificar en la primera linea de la sentencia la tabla o tablas en las que se quiere aplicar la eliminación de registros, y en la siguiente linea, comenzando con un FROM, la join de las tablas que necesitemos, y el criterio indicado con un WHERE.
Ejemplo SQL de Delete con Inner join
Un ejemplo sencillo de un delete con una join entre tablas para aplicar el criterio de borrado:
DELETE TablaConRegistrosAEliminar FROM TablaConRegistrosAEliminar TDel INNER JOIN TablaParaCriterioDeEliminacion TFiltro ON TDel.idCampo_fk=TFiltro.idCampo_pk WHERE TFiltro.criterio='Valor para filtrar'
Delete con Outer Join para eliminar registros sin referencias en otra tabla
Esta opción de utilizar una inner join para eliminar registros se puede utilizar con todos los tipos de join, de manera que si se combina con una outer join, por ejemplo, es muy útil para eliminar registros de una tabla cuyo identificador no se encuentre en otra tabla, es decir, registros de una tabla que no tengan correspondencia, o no existan en otra.
Sólo hay que hacer una left join con la tabla en la que tenemos los identificadores de referencia y todos los registros resultantes que tengan un valor nulo para la tabla enlazada son los que ese valor no está en la segunda tabla, es decir, los que queremos eliminar de la primera.
Como siempre, con un ejemplo de SQL para borrado de registros que no existen en otra tabla se entenderá mejor:
DELETE TablaConRegistrosAEliminar FROM TablaConRegistrosAEliminar TDel LEFT JOIN TablaConRegistrosBuenos TMaestra ON TDel.idCampo_fk=TMaestra.idCampo_pk WHERE TMaestra.idCampo_pk IS NULL
Delete con join sobre varias tablas para borrado de registros en cascada
En muchos casos puede interesar eliminar los registros de la tabla inicial, y también los registros de la tabla con la que se hace la join para establecer el criterio de selección. Sería algo así como una eliminación en cascada de los registros de ambas tablas.
Para eliminar a la vez todos los registros de ambas tablas, o más bien para hacer el delete de registros de varias tablas a la vez lo único que hay que hacer es incluir en la primera línea de la sentencia las tablas de las que se quiere eliminar los registros.
Para el ejemplo SQL de delete anterior, si quisiéramos borrar también los registros de la tabla 'enlazada' en la inner join para hacer una eliminación en cascada sólo habría que incluir la segunda tabla en la línea DELETE, de esta manera:
DELETE TablaConRegistrosAEliminar, TablaParaCriterioDeEliminacion FROM TablaConRegistrosAEliminar TDel INNER JOIN TablaParaCriterioDeEliminacion TFiltro ON TDel.idCampo_fk=TFiltro.idCampo_pk WHERE TFiltro.criterio='Valor para filtrar'
Precauciones antes de efectuar un delete
Una buena práctica muy recomendable antes de efectuar un borrado de registros, aparte de las habituales medidas de realización de backups implementadas en la base de datos, es consultar siempre los registros que se van a eliminar para no llevarse desagradables sorpresas una vez que ya se hayan eliminado.
La solución es tan sencilla como crear primero la sentencia de selección, y después de visualizar y validar los registros a eliminar, convertir el select en un delete, y proceder con el borrado. Para más seguridad puedes incluso hacer una copia temporal en otra tabla de justo los registros que vas a eliminar, por si más adelante descubres que alguno de ellos (o todos) no deberías haberlo eliminado, y tienes que hacer una recuperación lo más rápida posible.
Con la sintaxis antes comentada es tan fácil como sustituir la línea de 'delete [nombre_tabla]' por una línea 'select *', y si quieres la máxima seguridad y guardar esos registros, sólo tienes que añadir un 'into [nombre_tabla_backup]' a la linea 'select *'.
Yo además siempre juego con las líneas de comentarios para trabajar todo sobre la misma query, y asegurarme de que no haya diferencias entre el select y el delete.
Vamos a verlo por pasos con el primer ejemplo del post el ejemplo simple de delete con join:
1. Selección de los registros que quiero eliminar
Cambio el delete (comentado) por select, y visualizo los datos a borrar:
-- DELETE TablaConRegistrosAEliminar SELECT * FROM TablaConRegistrosAEliminar TDel INNER JOIN TablaParaCriterioDeEliminacion TFiltro ON TDel.idCampo_fk=TFiltro.idCampo_pk WHERE TFiltro.criterio='Valor para filtrar'
2. Guardo los registros en una tabla
Por si después necesito recuperar algo, o consultar lo que he eliminado:
-- DELETE TablaConRegistrosAEliminar SELECT * INTO TablaCopiaRegistrosAEliminar_yyyymmdd FROM TablaConRegistrosAEliminar TDel INNER JOIN TablaParaCriterioDeEliminacion TFiltro ON TDel.idCampo_fk=TFiltro.idCampo_pk WHERE TFiltro.criterio='Valor para filtrar'
3. Elimino los registros
Ya tengo más garantías de que no la voy a liar ;)
DELETE TablaConRegistrosAEliminar -- SELECT * FROM TablaConRegistrosAEliminar TDel INNER JOIN TablaParaCriterioDeEliminacion TFiltro ON TDel.idCampo_fk=TFiltro.idCampo_pk WHERE TFiltro.criterio='Valor para filtrar'
¿Y si al final la he liado y tengo que recuperar los registros eliminados?
Espero que este pequeño método sea de ayuda, no cuesta nada hacerlo, y puede evitar muchos problemas, la vuelta atrás después de un borrado siempre es un tema delicado que puede llegar a complicarse mucho.
En nuestro caso, si hemos tomado la precaución de guardar antes en una tabla los registros que vamos a eliminar, la recuperación de uno o más registros eliminados será tan sencilla como efectuar un insert en esta tabla con una select sobre la tabla en la que hemos copiado los datos a eliminar.
Algo así como esta query, que puede evitar tener que solicitar a nuestro DBA la restauración de un backup:
INSERT INTO TablaConRegistrosAEliminar SELECT * FROM TablaCopiaRegistrosAEliminar_yyyymmdd WHERE ...