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.