Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como selecionar todas as tabelas com nome de coluna e atualizar essa coluna


Não, não em uma única declaração.

Para obter os nomes de todas as tabelas que contêm a coluna chamada Foo :
SELECT table_schema, table_name
  FROM information_schema.columns 
  WHERE column_name = 'Foo'

Então, você precisaria de uma instrução UPDATE para cada tabela. (É possível atualizar várias tabelas em uma única instrução, mas isso precisaria ser uma junção cruzada (desnecessária).) É melhor fazer cada tabela separadamente.

Você pode usar SQL dinâmico para executar as instruções UPDATE em um programa armazenado MySQL (por exemplo, PROCEDURE)
  DECLARE sql VARCHAR(2000);
  SET sql = 'UPDATE db.tbl SET Foo = 0';
  PREPARE stmt FROM sql;
  EXECUTE stmt;
  DEALLOCATE stmt;

Se você declarar um cursor para a seleção de information_schema.tables, poderá usar um loop de cursor para processar um UPDATE dinâmico instrução para cada table_name retornado.
  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;

(Este é apenas um esboço de um exemplo, não verificado ou testado pela sintaxe.)

ACOMPANHAMENTO

Algumas breves notas sobre algumas ideias que provavelmente foram encobertas na resposta acima.

Para obter os nomes das tabelas que contêm a coluna Foo , podemos executar uma consulta do information_schema.columns tabela. (Essa é uma das tabelas fornecidas no MySQL information_schema base de dados.)

Como podemos ter tabelas em vários bancos de dados, o table_name não é suficiente para identificar uma tabela; precisamos saber em qual banco de dados a tabela está. Em vez de mexer com um "use db " antes de executarmos um UPDATE , podemos apenas referenciar a tabela UPDATE db.mytable SET Foo... .

Podemos usar nossa consulta de information_schema.columns para ir em frente e encadear (concatenar) as partes que precisamos criar para uma instrução UPDATE e fazer com que o SELECT retorne as instruções reais que precisaríamos executar para atualizar a coluna Foo , basicamente isso:
UPDATE `mydatabase`.`mytable` SET `Foo` = 0 

Mas queremos substituir os valores de table_schema e table_name no lugar de mydatabase e mytable . Se executarmos este SELECT
SELECT 'UPDATE `mydatabase`.`mytable` SET `Foo` = 0' AS sql

Isso nos retorna uma única linha, contendo uma única coluna (a coluna é chamada de sql , mas o nome da coluna não é importante para nós). O valor da coluna será apenas uma string. Mas a string que recebemos de volta é (esperamos) uma instrução SQL que poderíamos executar.

Teríamos a mesma coisa se partíssemos essa corda em pedaços e usássemos o CONCAT para juntá-los de volta para nós, por exemplo
SELECT CONCAT('UPDATE `','mydatabase','`.`','mytable','` SET `Foo` = 0') AS sql

Podemos usar essa consulta como um modelo para a instrução que queremos executar em information_schema.columns . Vamos substituir 'mydatabase' e 'mytable' com referências a colunas do information_schema.columns table que nos dá o banco de dados e 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'

Existem alguns bancos de dados que definitivamente não deseja atualizar... mysql , information_schema , performance_schema . Precisamos colocar na lista branca os bancos de dados que contêm a tabela que queremos atualizar
  AND c.table_schema IN ('mydatabase','anotherdatabase')

-ou - precisamos colocar na lista negra os bancos de dados que definitivamente não queremos atualizar
  AND c.table_schema NOT IN ('mysql','information_schema','performance_schema')

Podemos executar essa consulta (podemos adicionar um ORDER BY se quisermos que as linhas sejam retornadas em uma ordem específica) e o que recebemos de volta é uma lista contendo as instruções que queremos executar. Se salvarmos esse conjunto de strings como um arquivo de texto simples (excluindo linha de cabeçalho e formatação extra), adicionando um ponto e vírgula no final de cada linha, teríamos um arquivo que poderíamos executar a partir do mysql> cliente de linha de comando.

(Se algum dos itens acima for confuso, me avise.)

A próxima parte é um pouco mais complicada. O resto trata de uma alternativa para salvar a saída do SELECT como um arquivo de texto simples e executar as instruções do mysql cliente de linha de comando.

O MySQL fornece uma facilidade/recurso que nos permite executar basicamente qualquer string como uma instrução SQL, no contexto de um programa armazenado MySQL (por exemplo, um procedimento armazenado. O recurso que vamos usar é chamado SQL dinâmico .

Para usar SQL dinâmico , usamos as instruções PREPARE , EXECUTE e DEALLOCATE PREPARE . (O desalocar não é estritamente necessário, o MySQL limpará para nós se não o usarmos, mas acho que é uma boa prática fazê-lo de qualquer maneira.)

Novamente, SQL dinâmico está disponível SOMENTE no contexto de um programa armazenado MySQL. Para fazer isso, precisamos ter uma string contendo a instrução SQL que queremos executar. Como um exemplo simples, digamos que tivéssemos isso:
DECLARE str VARCHAR(2000);
SET str = 'UPDATE mytable SET mycol = 0 WHERE mycol < 0';

Para obter o conteúdo de str avaliado e executado como uma instrução SQL, o esquema básico é:
PREPARE stmt FROM str;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

A próxima parte complicada é juntar isso com a consulta que estamos executando para obter o valor da string que queremos executar como instruções SQL. Para fazer isso, montamos um loop de cursor. O esquema básico para isso é pegar nossa instrução SELECT:
SELECT bah FROM humbug

E transforme isso em uma definição de cursor:
DECLARE mycursor FOR SELECT bah FROM humbug ;

O que queremos é executar isso e percorrer as linhas que ele retorna. Para executar a instrução e preparar um conjunto de resultados, "abrimos" o cursor
OPEN mycursor; 

Quando terminarmos, vamos emitir um "close", para liberar o conjunto de resultados, para que o servidor MySQL saiba que não precisamos mais dele e possa limpar e liberar os recursos alocados para isso.
CLOSE mycursor;

Mas, antes de fechar o cursor, queremos "percorrer" o conjunto de resultados, buscando cada linha, e fazer algo com a linha. A instrução que usamos para obter a próxima linha do conjunto de resultados em uma variável de procedimento é:
FETCH mycursor INTO some_variable;

Antes de podermos buscar linhas em variáveis, precisamos definir as variáveis, por exemplo,
DECLARE some_variable VARCHAR(2000); 

Como nosso cursor (instrução SELECT) está retornando apenas uma única coluna, precisamos apenas de uma variável. Se tivéssemos mais colunas, precisaríamos de uma variável para cada coluna.

Eventualmente, teremos buscado a última linha do conjunto de resultados. Quando tentamos buscar o próximo, o MySQL vai lançar um erro.

Outras linguagens de programação nos permitiriam fazer um while loop, e vamos buscar as linhas e sair do loop quando processamos todas elas. MySQL é mais misterioso. Para fazer um loop:
mylabel: LOOP
  -- do something
END LOOP mylabel;

Isso por si só faz um loop infinito muito bom, porque esse loop não tem uma "saída". Felizmente, o MySQL nos dá o LEAVE instrução como uma maneira de sair de um loop. Normalmente, não queremos sair do loop na primeira vez que entramos, então geralmente há algum teste condicional que usamos para determinar se terminamos e devemos sair do loop ou não terminamos e devemos dar a volta o loop novamente.
 mylabel: LOOP
     -- do something useful
     IF some_condition THEN 
         LEAVE mylabel;
     END IF;
 END LOOP mylabel;

No nosso caso, queremos percorrer todas as linhas no conjunto de resultados, então vamos colocar um FETCH a a primeira instrução dentro do loop (o algo útil que queremos fazer).

Para obter uma ligação entre o erro que o MySQL lança quando tentamos buscar a última linha no conjunto de resultados e o teste condicional, temos que determinar se devemos deixar ...

O MySQL fornece uma maneira de definirmos um CONTINUE HANDLER (alguma declaração que queremos executar) quando o erro é lançado ...
 DECLARE CONTINUE HANDLER FOR NOT FOUND 

A ação que queremos realizar é definir uma variável como TRUE.
 SET done = TRUE;

Antes de podermos executar o SET, precisamos definir a variável:
 DECLARE done TINYINT(1) DEFAULT FALSE;

Com isso podemos alterar nosso LOOP para testar se o done variável é definida como TRUE, como a condição de saída, então nosso loop se parece com isso:
 mylabel: LOOP
     FETCH mycursor INTO some_variable;
     IF done THEN 
         LEAVE mylabel;
     END IF;
     -- do something with the row
 END LOOP mylabel;

O "faça algo com a linha" é onde queremos levar o conteúdo de some_variable e fazer algo útil com ele. Nosso cursor está nos retornando uma string que queremos executar como uma instrução SQL. E o MySQL nos dá o SQL dinâmico recurso que podemos usar para fazer isso.

NOTA:O MySQL possui regras sobre a ordem das instruções no procedimento. Por exemplo, o DECLARE declaração tem que vir no início. E acho que o CONTINUE HANDLER deve ser a última coisa declarada.

Novamente:O cursor e SQL dinâmico os recursos estão disponíveis SOMENTE no contexto de um programa armazenado MySQL, como um procedimento armazenado. O exemplo que dei acima foi apenas o exemplo do corpo de um procedimento.

Para criar isso como um procedimento armazenado, ele precisaria ser incorporado como parte de algo assim:
DELIMITER $$

DROP PROCEDURE IF EXISTS myproc $$

CREATE PROCEDURE myproc 
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN

   -- procedure body goes here

END$$

DELIMITER ;

Espero que isso explique o exemplo que dei com um pouco mais de detalhes.