Nee, niet in een enkele verklaring.
Om de namen te krijgen van alle tabellen die een kolom bevatten met de naam Foo
:
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'Foo'
Dan hebt u voor elke tabel een UPDATE-instructie nodig. (Het is mogelijk om meerdere tabellen in een enkele instructie bij te werken, maar dat zou een (onnodige) cross join moeten zijn.) Het is beter om elke tabel afzonderlijk te doen.
U kunt dynamische SQL gebruiken om de UPDATE-instructies in een MySQL-programma uit te voeren (bijv. PROCEDURE)
DECLARE sql VARCHAR(2000);
SET sql = 'UPDATE db.tbl SET Foo = 0';
PREPARE stmt FROM sql;
EXECUTE stmt;
DEALLOCATE stmt;
Als u een cursor declareert voor de select uit information_schema.tables, kunt u een cursorlus gebruiken om een dynamische UPDATE
te verwerken statement voor elke geretourneerde table_name.
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;
(Dit is slechts een ruwe schets van een voorbeeld, geen syntaxis gecontroleerd of getest.)
VERVOLG
Enkele korte opmerkingen over enkele ideeën die waarschijnlijk in het bovenstaande antwoord zijn verdoezeld.
Om de namen te krijgen van de tabellen met kolom Foo
, kunnen we een query uitvoeren vanuit de information_schema.columns
tafel. (Dat is een van de tabellen in het MySQL information_schema
database.)
Omdat we tabellen in meerdere databases kunnen hebben, is de tabelnaam niet voldoende om een tabel te identificeren; we moeten weten in welke database de tabel zich bevindt. In plaats van te mopperen met een "use db
" voordat we een UPDATE
uitvoeren , we kunnen gewoon verwijzen naar de tabel UPDATE db.mytable SET Foo...
.
We kunnen onze zoekopdracht van information_schema.columns
. gebruiken om door te gaan en de delen aan elkaar te rijgen (samenvoegen) die we moeten maken voor een UPDATE-instructie, en de SELECT de feitelijke instructies te laten retourneren die we zouden moeten uitvoeren om kolom Foo
bij te werken , eigenlijk dit:
UPDATE `mydatabase`.`mytable` SET `Foo` = 0
Maar we willen de waarden van table_schema
. vervangen en table_name
in plaats van mydatabase
en mytable
. Als we dit uitvoeren SELECT
SELECT 'UPDATE `mydatabase`.`mytable` SET `Foo` = 0' AS sql
Dat levert ons een enkele rij op, die een enkele kolom bevat (de kolom heet toevallig sql
, maar de naam van de kolom is niet belangrijk voor ons). De waarde van de kolom is slechts een tekenreeks. Maar de string die we terugkrijgen is (hopelijk) een SQL-instructie die we zouden kunnen uitvoeren.
We zouden hetzelfde krijgen als we dat touw in stukken zouden breken en CONCAT zouden gebruiken om ze voor ons weer aan elkaar te rijgen, bijvoorbeeld
SELECT CONCAT('UPDATE `','mydatabase','`.`','mytable','` SET `Foo` = 0') AS sql
We kunnen die query gebruiken als een model voor de instructie die we willen uitvoeren tegen information_schema.columns
. We vervangen 'mydatabase'
en 'mytable'
met verwijzingen naar kolommen uit de information_schema.columns
tabel die ons de database en table_name geeft.
SELECT CONCAT('UPDATE `',c.table_schema,'`.`',c.table_name,'` SET `Foo` = 0') AS sql
FROM information_schema.columns
WHERE c.column_name = 'Foo'
Er zijn enkele databases die we zeker niet doen wil updaten... mysql
, information_schema
, performance_schema
. We moeten ofwel de databases op de witte lijst zetten die de tabel bevatten die we willen bijwerken
AND c.table_schema IN ('mydatabase','anotherdatabase')
-of - we moeten de databases die we absoluut niet willen updaten op de zwarte lijst zetten
AND c.table_schema NOT IN ('mysql','information_schema','performance_schema')
We kunnen die query uitvoeren (we zouden een ORDER BY
kunnen toevoegen als we willen dat de rijen in een bepaalde volgorde worden geretourneerd) en wat we terugkrijgen is een lijst met de instructies die we willen uitvoeren. Als we die reeks tekenreeksen zouden opslaan als een tekstbestand zonder opmaak (exclusief koprij en extra opmaak), en een puntkomma aan het einde van elke regel zouden toevoegen, zouden we een bestand hebben dat we zouden kunnen uitvoeren vanuit de mysql>
opdrachtregelclient.
(Als een van de bovenstaande dingen verwarrend is, laat het me dan weten.)
Het volgende deel is iets ingewikkelder. De rest hiervan behandelt een alternatief voor het opslaan van de uitvoer van de SELECT als een tekstbestand zonder opmaak en het uitvoeren van de instructies van de mysql
opdrachtregelclient.
MySQL biedt een faciliteit/functie waarmee we in principe elke . kunnen uitvoeren tekenreeks als een SQL-instructie, in de context van een opgeslagen MySQL-programma (bijvoorbeeld een opgeslagen procedure. De functie die we gaan gebruiken heet dynamische SQL .
dynamische SQL gebruiken , gebruiken we de statements PREPARE
, EXECUTE
en DEALLOCATE PREPARE
. (De deallocate is niet strikt noodzakelijk, MySQL zal voor ons opschonen als we het niet gebruiken, maar ik denk dat het een goede gewoonte is om het toch te doen.)
Nogmaals, dynamische SQL is ALLEEN beschikbaar in de context van een MySQL opgeslagen programma. Om dit te doen, hebben we een string nodig die de SQL-instructie bevat die we willen uitvoeren. Laten we als eenvoudig voorbeeld zeggen dat we dit hadden:
DECLARE str VARCHAR(2000);
SET str = 'UPDATE mytable SET mycol = 0 WHERE mycol < 0';
Om de inhoud van str
. te krijgen geëvalueerd en uitgevoerd als een SQL-instructie, de basisstructuur is:
PREPARE stmt FROM str;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Het volgende gecompliceerde deel is om dat samen te voegen met de query die we uitvoeren om de tekenreekswaarde te krijgen die we willen uitvoeren als SQL-instructies. Om dat te doen, hebben we een cursorlus samengesteld. Het basisschema daarvoor is om onze SELECT-instructie te nemen:
SELECT bah FROM humbug
En verander dat in een cursordefinitie:
DECLARE mycursor FOR SELECT bah FROM humbug ;
Wat we willen is dat uitvoeren en door de rijen lopen die het retourneert. Om de instructie uit te voeren en een resultatenset voor te bereiden, "openen" we de cursor
OPEN mycursor;
Als we hiermee klaar zijn, gaan we een "close" uitgeven om de resultatenset vrij te geven, zodat de MySQL-server weet dat we deze niet meer nodig hebben, en kan opschonen en de daaraan toegewezen middelen vrijmaken.
CLOSE mycursor;
Maar voordat we de cursor sluiten, willen we "doorlopen" door de resultatenset, elke rij ophalen en iets met de rij doen. Het statement dat we gebruiken om de volgende rij uit de resultatenset in een procedurevariabele te krijgen is:
FETCH mycursor INTO some_variable;
Voordat we rijen in variabelen kunnen ophalen, moeten we de variabelen definiëren, bijvoorbeeld
DECLARE some_variable VARCHAR(2000);
Omdat onze cursor (SELECT-instructie) slechts één kolom retourneert, hebben we maar één variabele nodig. Als we meer kolommen hadden, hadden we voor elke kolom een variabele nodig.
Uiteindelijk hebben we de laatste rij uit de resultatenset opgehaald. Wanneer we proberen de volgende op te halen, geeft MySQL een foutmelding.
Andere programmeertalen zouden ons gewoon een while
laten doen lus, en laten we de rijen ophalen en de lus verlaten als we ze allemaal hebben verwerkt. MySQL is geheimzinniger. Een lus maken:
mylabel: LOOP
-- do something
END LOOP mylabel;
Dat op zich zorgt voor een hele fijne oneindige lus, omdat die lus geen "uitgang" heeft. Gelukkig geeft MySQL ons de LEAVE
statement als een manier om een lus te verlaten. We willen de lus meestal niet verlaten als we hem voor het eerst betreden, dus er is meestal een voorwaardelijke test die we gebruiken om te bepalen of we klaar zijn, en de lus moeten verlaten, of dat we niet klaar zijn, en rond moeten gaan de lus weer.
mylabel: LOOP
-- do something useful
IF some_condition THEN
LEAVE mylabel;
END IF;
END LOOP mylabel;
In ons geval willen we alle rijen in de resultatenset doorlopen, dus we gaan een FETCH
plaatsen a de eerste instructie binnen de lus (het iets nuttigs dat we willen doen).
Om een koppeling te krijgen tussen de fout die MySQL genereert wanneer we proberen voorbij de laatste rij in de resultatenset te halen, en de voorwaardelijke test moeten we bepalen of we moeten vertrekken...
MySQL biedt ons een manier om een CONTINUE HANDLER
te definiëren (een verklaring die we willen uitvoeren) wanneer de fout wordt gegenereerd...
DECLARE CONTINUE HANDLER FOR NOT FOUND
De actie die we willen uitvoeren is om een variabele in te stellen op TRUE.
SET done = TRUE;
Voordat we de SET kunnen uitvoeren, moeten we de variabele definiëren:
DECLARE done TINYINT(1) DEFAULT FALSE;
Daarmee kunnen we onze LOOP veranderen om te testen of de done
variabele is ingesteld op TRUE, als de exit-voorwaarde, dus onze lus ziet er ongeveer zo uit:
mylabel: LOOP
FETCH mycursor INTO some_variable;
IF done THEN
LEAVE mylabel;
END IF;
-- do something with the row
END LOOP mylabel;
De "doe iets met de rij" is waar we de inhoud van some_variable
naartoe willen nemen en doe er iets nuttigs mee. Onze cursor geeft ons een string terug die we willen uitvoeren als een SQL-instructie. En MySQL geeft ons de dynamische SQL functie die we daarvoor kunnen gebruiken.
OPMERKING:MySQL heeft regels over de volgorde van de instructies in de procedure. Bijvoorbeeld de DECLARE
verklaring moet aan het begin komen. En ik denk dat de CONTINUE HANDLER het laatste moet zijn dat wordt aangegeven.
Nogmaals:de cursor en dynamische SQL functies zijn ALLEEN beschikbaar in de context van een opgeslagen MySQL-programma, zoals een opgeslagen procedure. Het voorbeeld dat ik hierboven gaf was alleen het voorbeeld van de body van een procedure.
Om dit te creëren als een opgeslagen procedure, zou het moeten worden opgenomen als onderdeel van zoiets als dit:
DELIMITER $$
DROP PROCEDURE IF EXISTS myproc $$
CREATE PROCEDURE myproc
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
-- procedure body goes here
END$$
DELIMITER ;
Hopelijk verklaart dat het voorbeeld dat ik heb gegeven wat gedetailleerder.