En ocasiones cuando queremos manipular – borrar o editar – los registros de una tabla no tenemos los identificadores – ids que generalmente son la llave primaria P.K. – para poder realizar la manipulación directamente.
Algo que podemos hacer es una subconsulta – consulta anidada – para que, con base en el criterio de la consulta, obtengamos los identificadores de los registros que queremos manipular.
El único detalle es que, cuando la tabla hace referencia a sí misma, probablemente se nos muestre el siguiente mensaje de error – sustituir ‘nombre_de_la_tabla’ con el nombre de la tabla que estás utilizando en tu consulta – :
You can't specify target table 'nombre_de_la_tabla' for update in FROM clause
En el siguiente ejemplo tenemos una tabla de categorías / subcategorías la cual utiliza una estructura de árbol para almacenar los datos.
Las categorías principales / base tienen el valor sin definir – NULL – en la columna parent_id.
Las subcategorías tienen el id de la categoría base en la columna parent_id, es decir, la categoría padre es un registro que se encuentra dentro de la misma tabla.
¿Qué tendríamos que hacer para actualizar la fecha de creación para las categorías relacionadas con xml?
-- Obtener el id de la categoría padre de XML
SELECT * FROM categorias WHERE nombre LIKE "%xml%";
-- Obtener los registros de la categoría y las subcategorías
-- En este caso el id de la categoría padre es 1
SELECT * FROM categorias WHERE id = 1 OR parent_id = 1;
-- Actualizar los registros
UPDATE categorias SET creado = NOW() WHERE id = 1 OR parent_id = 1;
Esto puede funcionar bien, pero si queremos hacer funcionar las consultas anidadas para este propósito podríamos usar un alias de tabla para evitar el error.
-- Seleccionar el id de la(s) categoría(s) donde el valor que se encuentre en la columna parent_id o id
-- sea el valor que esté en la columna parent_id de la categoría con el nombre que contenga el término xml
SELECT id FROM categorias WHERE parent_id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%') OR id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%');
-- Si intentamos actualizar el valor de la tabla directamente nos marca un error
UPDATE categorias SET actualizado = NOW() WHERE id IN (SELECT id FROM categorias WHERE parent_id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%') OR id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%'));
-- Utilizando un alias de tabla
-- La consulta funciona tanto para actualizar...
UPDATE categorias SET actualizado = NOW() WHERE id IN (
SELECT id FROM (
SELECT id FROM categorias WHERE parent_id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%') OR id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%'))
AS tabla_alias);
-- ...como también funciona para borrar:
DELETE FROM categorias WHERE id IN (
SELECT id FROM (
SELECT id FROM categorias WHERE parent_id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%') OR id IN (SELECT parent_id FROM categorias WHERE nombre LIKE '%xml%'))
AS tabla_alias)
La manera en que funciona es la siguiente:
-- Realizamos la consulta interna para obtener el / los
-- identificadores.
SELECT id FROM tabla WHERE columna LIKE "%término%"
-- El resultado de esa consulta es un subconjunto * de
-- la tabla con la que estamos trabajando
-- * es la columna id, el número de renglones varía
-- dependiendo del término de búsqueda utilizado
--
-- Para que se pueda realizar la manipulación
-- (actualización / borrado) en esa tabla tenemos
-- que convertir el resultado en otra tabla.
--
-- La sintaxis básica de una consulta es:
SELECT columna(s) FROM (tabla)
-- En este caso, la columna que queremos obtener es id
SELECT id FROM (tabla)
-- La tabla a la que hacemos referencia es
-- a la tabla obtenida por el resultado de la consulta
-- Es por eso que, en lugar del nombre de la tabla,
-- vamos a envolver la consulta entre paréntesis
-- Después de la consulta le asignamos un nombre
-- como alias de la tabla.
-- Podemos usar u omitir la palabra AS entre la consulta
-- y el nombre del alias (es opcional).
SELECT id FROM (SELECT id FROM tabla
WHERE columna LIKE "%término%") alias_tabla
SELECT id FROM (SELECT id FROM tabla
WHERE columna LIKE "%término%") AS alias_tabla
-- Toda la consulta para actualizar
UPDATE tabla SET columna1 = valor1, columna2 = valor2 WHERE id IN (
SELECT id FROM (SELECT id FROM tabla
WHERE columna LIKE "%término%") AS alias_tabla);
-- Toda la consulta para borrar
DELETE FROM tabla WHERE id IN (
SELECT id FROM (SELECT id FROM tabla
WHERE columna LIKE "%término%") AS x);
De esa manera MySQL no considera que la tabla utilizada en la subconsulta es la misma tabla donde va a actualizar / borrar los registros y por eso lo puede hacer.
El archivo / script para crear la tabla está en https://gist.github.com/eamexicano/fbe63f74241004a98cae21cd00567ee6.