sql >> Databasteknik >  >> RDS >> Mysql

Hur man väljer alla tabeller med kolumnnamn och uppdaterar den kolumnen

Nej, inte i ett enda uttalande.

För att få namnen på alla tabeller som innehåller kolumn med namnet Foo :

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

Sedan skulle du behöva en UPDATE-sats för varje tabell. (Det är möjligt att uppdatera flera tabeller i en enda sats, men det skulle behöva vara en (onödig) korskoppling.) Det är bättre att göra varje tabell separat.

Du kan använda dynamisk SQL för att köra UPDATE-satserna i ett MySQL-lagrat program (t.ex. PROCEDURE)

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

Om du deklarerar en markör för select from information_schema.tables kan du använda en markörslinga för att bearbeta en dynamisk UPDATE uttalande för varje returnerad tabellnamn.

  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;

(Detta är bara en grov översikt av ett exempel, inte syntax kontrollerad eller testad.)

UPPFÖLJNING

Några korta anteckningar om några idéer som förmodligen försvann i svaret ovan.

För att få namnen på tabellerna som innehåller kolumnen Foo , kan vi köra en fråga från information_schema.columns tabell. (Det är en av tabellerna i MySQL information_schema databas.)

Eftersom vi kan ha tabeller i flera databaser är tabellnamnet inte tillräckligt för att identifiera en tabell; vi behöver veta vilken databas tabellen finns i. Istället för att smutskasta med en "use db " uttalande innan vi kör en UPDATE , vi kan bara referera till tabellen UPDATE db.mytable SET Foo... .

Vi kan använda vår fråga om information_schema.columns att gå vidare och sätta ihop (sammanfoga) delarna vi behöver skapa för en UPDATE-sats, och låta SELECT returnera de faktiska satserna vi skulle behöva köra för att uppdatera kolumnen Foo , i princip detta:

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

Men vi vill ersätta värdena från table_schema och table_name i stället för mydatabase och mytable . Om vi ​​kör detta SELECT

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

Det ger oss en enda rad som innehåller en enda kolumn (kolumnen råkar heta sql , men namnet på kolumnen är inte viktigt för oss). Värdet på kolumnen kommer bara att vara en sträng. Men strängen vi får tillbaka råkar vara (hoppas vi) en SQL-sats som vi skulle kunna köra.

Vi skulle få samma sak om vi bröt upp det snöret i bitar och använde CONCAT för att dra ihop dem igen åt oss, t.ex.

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

Vi kan använda den frågan som en modell för satsen vi vill köra mot information_schema.columns . Vi kommer att ersätta 'mydatabase' och 'mytable' med referenser till kolumner från information_schema.columns tabell som ger oss databasen och tabellnamn.

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

Det finns vissa databaser som vi definitivt inte gör vill uppdatera... mysql , information_schema , performance_schema . Vi behöver antingen vitlista databaserna som innehåller tabellen vi vill uppdatera

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

-eller - Vi måste svartlista de databaser vi definitivt inte vill uppdatera

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

Vi kan köra den frågan (vi kan lägga till en ORDER BY om vi vill att raderna ska returneras i en viss ordning) och vad vi får tillbaka är en lista som innehåller de satser vi vill köra. Om vi ​​sparade den uppsättningen strängar som en vanlig textfil (exklusive rubrikrad och extra formatering), och lägger till ett semikolon i slutet av varje rad, skulle vi ha en fil som vi kunde köra från mysql> kommandoradsklient.

(Om något av ovanstående är förvirrande, låt mig veta.)

Nästa del är lite mer komplicerad. Resten av detta handlar om ett alternativ till att spara utdata från SELECT som en vanlig textfil och köra satserna från mysql kommandoradsklient.

MySQL tillhandahåller en funktion/funktion som gör att vi kan köra i princip alla sträng som en SQL-sats, i sammanhanget av ett MySQL-lagrat program (till exempel en lagrad procedur. Funktionen vi ska använda heter dynamisk SQL .

För att använda dynamisk SQL , använder vi satserna PREPARE , EXECUTE och DEALLOCATE PREPARE . (Deallokeringen är inte strikt nödvändig, MySQL kommer att städa åt oss om vi inte använder det, men jag tror att det är bra att göra det ändå.)

Återigen, dynamisk SQL är ENDAST tillgänglig i ett MySQL-lagrat program. För att göra detta måste vi ha en sträng som innehåller SQL-satsen vi vill exekvera. Som ett enkelt exempel, låt oss säga att vi hade detta:

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

För att få innehållet i str utvärderas och exekveras som en SQL-sats är grundkonturen:

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

Nästa komplicerade del är att sätta det tillsammans med frågan vi kör för att få ett strängvärde som vi vill köra som SQL-satser. För att göra det sätter vi ihop en markörslinga. Den grundläggande dispositionen för det är att ta vår SELECT-sats:

SELECT bah FROM humbug

Och förvandla det till en markördefinition:

DECLARE mycursor FOR SELECT bah FROM humbug ;

Vad vi vill är att utföra det och gå igenom raderna som det returnerar. För att köra satsen och förbereda en resultatuppsättning "öppnar" vi markören

OPEN mycursor; 

När vi är klara med det, kommer vi att utfärda ett "stäng", för att släppa resultatuppsättningen, så att MySQL-servern vet att vi inte behöver den längre, och kan städa och frigöra resurserna som tilldelats det.

CLOSE mycursor;

Men innan vi stänger markören vill vi "loopa" genom resultatuppsättningen, hämta varje rad och göra något med raden. Satsen vi använder för att få nästa rad från resultatuppsättningen till en procedurvariabel är:

FETCH mycursor INTO some_variable;

Innan vi kan hämta rader till variabler måste vi definiera variablerna, t.ex.

DECLARE some_variable VARCHAR(2000); 

Eftersom vår markör (SELECT-sats) endast returnerar en enda kolumn, behöver vi bara en variabel. Om vi ​​hade fler kolumner skulle vi behöva en variabel för varje kolumn.

Så småningom kommer vi att ha hämtat den sista raden från resultatuppsättningen. När vi försöker hämta nästa kommer MySQL att ge ett felmeddelande.

Andra programmeringsspråk skulle låta oss göra en while loop, och låt oss hämta raderna och lämna loopen när vi har bearbetat dem alla. MySQL är mer mystisk. För att göra en loop:

mylabel: LOOP
  -- do something
END LOOP mylabel;

Det i sig ger en mycket fin oändlig loop, eftersom den loopen inte har någon "utgång". Lyckligtvis ger MySQL oss LEAVE uttalande som ett sätt att lämna en loop. Vi vill vanligtvis inte lämna slingan första gången vi går in i den, så det finns vanligtvis något villkorligt test som vi använder för att avgöra om vi är klara, och bör lämna slingan, eller så är vi inte klara, och bör gå runt slingan igen.

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

I vårt fall vill vi gå igenom alla rader i resultatuppsättningen, så vi lägger en FETCH a det första påståendet i slingan (det nyttiga vi vill göra).

För att få en koppling mellan felet som MySQL kastar när vi försöker hämta förbi den sista raden i resultatuppsättningen, och det villkorliga testet måste vi avgöra om vi ska lämna...

MySQL tillhandahåller ett sätt för oss att definiera en CONTINUE HANDLER (något uttalande vi vill ha utfört) när felet kastas...

 DECLARE CONTINUE HANDLER FOR NOT FOUND 

Åtgärden vi vill utföra är att ställa in en variabel till TRUE.

 SET done = TRUE;

Innan vi kan köra SET måste vi definiera variabeln:

 DECLARE done TINYINT(1) DEFAULT FALSE;

Med det kan vi ändra vår LOOP för att testa om den done variabeln är satt till TRUE, som exitvillkor, så vår loop ser ut ungefär så här:

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

"Gör något med raden" är där vi vill ta innehållet i some_variable och göra något användbart med det. Vår markör returnerar oss en sträng som vi vill köra som en SQL-sats. Och MySQL ger oss den dynamiska SQL funktion som vi kan använda för att göra det.

OBS:MySQL har regler om ordningen på uttalandena i proceduren. Till exempel DECLARE uttalande måste komma i början. Och jag tror att CONTINUE HANDLER måste vara det sista som deklareras.

Återigen:markören och dynamisk SQL funktioner är ENDAST tillgängliga i ett MySQL-lagrat program, såsom en lagrad procedur. Exemplet jag gav ovan var bara exemplet på kroppen av ett förfarande.

För att få detta skapat som en lagrad procedur, skulle det behöva inkorporeras som en del av något sånt här:

DELIMITER $$

DROP PROCEDURE IF EXISTS myproc $$

CREATE PROCEDURE myproc 
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN

   -- procedure body goes here

END$$

DELIMITER ;

Förhoppningsvis förklarar det exemplet jag gav lite mer detaljerat.



  1. Söker efter de 5 platserna som ligger närmast ett postnummer – vilken väg ska jag gå?

  2. MySQL-strängseparation med kommaoperator

  3. FEL 1452 (23000):Det går inte att lägga till eller uppdatera en underordnad rad:en begränsning av främmande nyckel misslyckas

  4. MAA-dokumentation för Oracle Cloud