sql >> Base de Datos >  >> RDS >> Mysql

Cómo seleccionar todas las tablas con nombre de columna y actualizar esa columna

No, no en una sola declaración.

Para obtener los nombres de todas las tablas que contienen una columna llamada Foo :

SELECT table_schema, table_name
  FROM information_schema.columns 
  WHERE column_name = 'Foo'

Luego, necesitaría una declaración de ACTUALIZACIÓN para cada tabla. (Es posible actualizar varias tablas en una sola declaración, pero eso tendría que ser una unión cruzada (innecesaria).) Es mejor hacer cada tabla por separado.

Puede usar SQL dinámico para ejecutar las declaraciones de ACTUALIZACIÓN en un programa almacenado de MySQL (por ejemplo, PROCEDIMIENTO)

  DECLARE sql VARCHAR(2000);
  SET sql = 'UPDATE db.tbl SET Foo = 0';
  PREPARE stmt FROM sql;
  EXECUTE stmt;
  DEALLOCATE stmt;

Si declara un cursor para la selección de information_schema.tables, puede usar un bucle de cursor para procesar un UPDATE dinámico instrucción para cada table_name devuelto.

  DECLARE done TINYINT(1) DEFAULT FALSE;
  DECLARE sql  VARCHAR(2000);

  DECLARE csr FOR
  SELECT CONCAT('UPDATE `',c.table_schema,'`.`',c.table_name,'` SET `Foo` = 0') AS sql
    FROM information_schema.columns c
   WHERE c.column_name = 'Foo'
     AND c.table_schema NOT IN ('mysql','information_schema','performance_schema');
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  OPEN csr;
  do_foo: LOOP
     FETCH csr INTO sql;
     IF done THEN
        LEAVE do_foo;
     END IF;
     PREPARE stmt FROM sql;
     EXECUTE stmt;
     DEALLOCATE PREPARE stmt;
  END LOOP do_foo;
  CLOSE csr;

(Esto es solo un resumen aproximado de un ejemplo, sin verificación ni prueba de sintaxis).

SEGUIMIENTO

Algunas notas breves sobre algunas ideas que probablemente se pasaron por alto en la respuesta anterior.

Para obtener los nombres de las tablas que contienen la columna Foo , podemos ejecutar una consulta desde information_schema.columns mesa. (Esa es una de las tablas provistas en MySQL information_schema base de datos.)

Debido a que podemos tener tablas en varias bases de datos, table_name no es suficiente para identificar una tabla; necesitamos saber en qué base de datos se encuentra la tabla. En lugar de jugar con "use db " declaración antes de que ejecutemos una UPDATE , solo podemos hacer referencia a la tabla UPDATE db.mytable SET Foo... .

Podemos usar nuestra consulta de information_schema.columns para seguir adelante y unir (concatenar) las partes que necesitamos crear para una instrucción UPDATE, y hacer que SELECT devuelva las declaraciones reales que necesitaríamos ejecutar para actualizar la columna Foo , básicamente esto:

UPDATE `mydatabase`.`mytable` SET `Foo` = 0 

Pero queremos sustituir los valores de table_schema y table_name en lugar de mydatabase y mytable . Si ejecutamos este SELECT

SELECT 'UPDATE `mydatabase`.`mytable` SET `Foo` = 0' AS sql

Eso nos devuelve una sola fila, que contiene una sola columna (la columna se llama sql , pero el nombre de la columna no es importante para nosotros). El valor de la columna será solo una cadena. Pero la cadena que obtenemos es (esperamos) una declaración SQL que podríamos ejecutar.

Obtendríamos lo mismo si rompiéramos esa cadena en pedazos y usáramos CONCAT para volver a unirlos, por ejemplo,

SELECT CONCAT('UPDATE `','mydatabase','`.`','mytable','` SET `Foo` = 0') AS sql

Podemos usar esa consulta como modelo para la declaración que queremos ejecutar contra information_schema.columns . Reemplazaremos 'mydatabase' y 'mytable' con referencias a columnas de information_schema.columns table que nos da la base de datos y table_name.

SELECT CONCAT('UPDATE `',c.table_schema,'`.`',c.table_name,'` SET `Foo` = 0') AS sql
  FROM information_schema.columns 
 WHERE c.column_name = 'Foo'

Hay algunas bases de datos que definitivamente no quiero actualizar... mysql , information_schema , performance_schema . Necesitamos incluir en la lista blanca las bases de datos que contienen la tabla que queremos actualizar

  AND c.table_schema IN ('mydatabase','anotherdatabase')

-o - necesitamos incluir en la lista negra las bases de datos que definitivamente no queremos actualizar

  AND c.table_schema NOT IN ('mysql','information_schema','performance_schema')

Podemos ejecutar esa consulta (podríamos agregar un ORDER BY si queremos que las filas se devuelvan en un orden particular) y lo que obtenemos es una lista que contiene las declaraciones que queremos ejecutar. Si guardamos ese conjunto de cadenas como un archivo de texto sin formato (excluyendo la fila de encabezado y el formato adicional), agregando un punto y coma al final de cada línea, tendríamos un archivo que podríamos ejecutar desde mysql> cliente de línea de comandos.

(Si alguno de los anteriores es confuso, hágamelo saber).

La siguiente parte es un poco más complicada. El resto de esto trata de una alternativa para guardar la salida de SELECT como un archivo de texto sin formato y ejecutar las declaraciones de mysql cliente de línea de comandos.

MySQL proporciona una función/característica que nos permite ejecutar básicamente cualquier cadena como una instrucción SQL, en el contexto de un programa almacenado de MySQL (por ejemplo, un procedimiento almacenado. La característica que vamos a utilizar se llama SQL dinámico .

Para usar SQL dinámico , usamos las sentencias PREPARE , EXECUTE y DEALLOCATE PREPARE . (La desasignación no es estrictamente necesaria, MySQL lo limpiará si no lo usamos, pero creo que es una buena práctica hacerlo de todos modos).

De nuevo, SQL dinámico está disponible SOLO en el contexto de un programa almacenado MySQL. Para hacer esto, necesitamos tener una cadena que contenga la instrucción SQL que queremos ejecutar. Como ejemplo simple, digamos que tenemos esto:

DECLARE str VARCHAR(2000);
SET str = 'UPDATE mytable SET mycol = 0 WHERE mycol < 0';

Para obtener el contenido de str evaluado y ejecutado como una instrucción SQL, el esquema básico es:

PREPARE stmt FROM str;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

La siguiente parte complicada es unir eso con la consulta que estamos ejecutando para obtener el valor de cadena que queremos ejecutar como declaraciones SQL. Para hacer eso, armamos un bucle de cursor. El esquema básico para eso es tomar nuestra instrucción SELECT:

SELECT bah FROM humbug

Y convertir eso en una definición de cursor:

DECLARE mycursor FOR SELECT bah FROM humbug ;

Lo que queremos es ejecutar eso y recorrer las filas que devuelve. Para ejecutar la instrucción y preparar un conjunto de resultados, "abrimos" el cursor

OPEN mycursor; 

Cuando terminemos, vamos a emitir un "cierre" para publicar el conjunto de resultados, de modo que el servidor MySQL sepa que ya no lo necesitamos, y pueda limpiar y liberar los recursos asignados a eso.

CLOSE mycursor;

Pero, antes de cerrar el cursor, queremos "recorrer" el conjunto de resultados, obtener cada fila y hacer algo con la fila. La declaración que usamos para obtener la siguiente fila del conjunto de resultados en una variable de procedimiento es:

FETCH mycursor INTO some_variable;

Antes de que podamos obtener filas en variables, debemos definir las variables, por ejemplo,

DECLARE some_variable VARCHAR(2000); 

Dado que nuestro cursor (sentencia SELECT) devuelve solo una columna, solo necesitamos una variable. Si tuviéramos más columnas, necesitaríamos una variable para cada columna.

Eventualmente, habremos obtenido la última fila del conjunto de resultados. Cuando intentemos obtener el siguiente, MySQL arrojará un error.

Otros lenguajes de programación nos permitirían hacer un while loop, y busquemos las filas y salgamos del loop cuando las hayamos procesado todas. MySQL es más arcano. Para hacer un bucle:

mylabel: LOOP
  -- do something
END LOOP mylabel;

Eso por sí solo crea un bucle infinito muy fino, porque ese bucle no tiene una "salida". Afortunadamente, MySQL nos da el LEAVE declaración como una forma de salir de un bucle. Por lo general, no queremos salir del ciclo la primera vez que ingresamos, por lo que generalmente hay una prueba condicional que usamos para determinar si hemos terminado y debemos salir del ciclo, o si no hemos terminado y debemos dar la vuelta. el bucle de nuevo.

 mylabel: LOOP
     -- do something useful
     IF some_condition THEN 
         LEAVE mylabel;
     END IF;
 END LOOP mylabel;

En nuestro caso, queremos recorrer todas las filas del conjunto de resultados, por lo que vamos a poner un FETCH a la primera declaración dentro del ciclo (algo útil que queremos hacer).

Para obtener un vínculo entre el error que arroja MySQL cuando intentamos pasar la última fila en el conjunto de resultados y la prueba condicional, tenemos que determinar si debemos dejar...

MySQL nos proporciona una forma de definir un CONTINUE HANDLER (alguna declaración que queremos realizar) cuando se lanza el error...

 DECLARE CONTINUE HANDLER FOR NOT FOUND 

La acción que queremos realizar es establecer una variable en VERDADERO.

 SET done = TRUE;

Antes de que podamos ejecutar el SET, necesitamos definir la variable:

 DECLARE done TINYINT(1) DEFAULT FALSE;

Con eso, podemos cambiar nuestro LOOP para probar si done la variable se establece en VERDADERO, como condición de salida, por lo que nuestro ciclo se parece a esto:

 mylabel: LOOP
     FETCH mycursor INTO some_variable;
     IF done THEN 
         LEAVE mylabel;
     END IF;
     -- do something with the row
 END LOOP mylabel;

El "hacer algo con la fila" es donde queremos llevar el contenido de some_variable y hacer algo útil con él. Nuestro cursor nos devuelve una cadena que queremos ejecutar como una declaración SQL. Y MySQL nos da el SQL dinámico característica que podemos usar para hacer eso.

NOTA:MySQL tiene reglas sobre el orden de las sentencias en el procedimiento. Por ejemplo, DECLARE declaración tiene que venir al principio. Y creo que CONTINUE HANDLER tiene que ser lo último que se declare.

De nuevo:El cursor y SQL dinámico las funciones están disponibles SÓLO en el contexto de un programa almacenado de MySQL, como un procedimiento almacenado. El ejemplo que di arriba fue solo el ejemplo del cuerpo de un procedimiento.

Para crear esto como un procedimiento almacenado, sería necesario incorporarlo como parte de algo como esto:

DELIMITER $$

DROP PROCEDURE IF EXISTS myproc $$

CREATE PROCEDURE myproc 
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN

   -- procedure body goes here

END$$

DELIMITER ;

Con suerte, eso explica el ejemplo que di con un poco más de detalle.