Mysql
 sql >> Database >  >> RDS >> Mysql

Come selezionare tutte le tabelle con il nome della colonna e aggiornare quella colonna

No, non in una sola affermazione.

Per ottenere i nomi di tutte quelle tabelle che contengono la colonna denominata Foo :

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

Quindi, avresti bisogno di un'istruzione UPDATE per ogni tabella. (È possibile aggiornare più tabelle in una singola istruzione, ma dovrebbe essere un cross join (non necessario). È meglio eseguire ciascuna tabella separatamente.

È possibile utilizzare l'SQL dinamico per eseguire le istruzioni UPDATE in un programma memorizzato in MySQL (ad es. PROCEDURE)

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

Se dichiari un cursore per la selezione da information_schema.tables, puoi utilizzare un ciclo del cursore per elaborare un UPDATE dinamico istruzione per ogni nome_tabella restituito.

  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;

(Questa è solo una bozza di un esempio, non verificata o verificata la sintassi.)

SEGUITO

Alcune brevi note su alcune idee che probabilmente sono state ignorate nella risposta sopra.

Per ottenere i nomi delle tabelle contenenti la colonna Foo , possiamo eseguire una query da information_schema.columns tavolo. (Questa è una delle tabelle fornite in MySQL information_schema banca dati.)

Poiché potremmo avere tabelle in più database, table_name non è sufficiente per identificare una tabella; abbiamo bisogno di sapere in quale database si trova la tabella. Piuttosto che smanettare con un "use db " prima di eseguire un UPDATE , possiamo semplicemente fare riferimento alla tabella UPDATE db.mytable SET Foo... .

Possiamo usare la nostra query di information_schema.columns per andare avanti e mettere insieme (concatenare) le parti che dobbiamo creare per un'istruzione UPDATE e fare in modo che SELECT restituisca le istruzioni effettive che dovremmo eseguire per aggiornare la colonna Foo , sostanzialmente questo:

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

Ma vogliamo sostituire i valori da table_schema e table_name al posto di mydatabase e mytable . Se eseguiamo questo SELECT

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

Questo ci restituisce una singola riga, contenente una singola colonna (la colonna sembra essere denominata sql , ma il nome della colonna non è importante per noi). Il valore della colonna sarà solo una stringa. Ma la stringa che otteniamo è (speriamo) un'istruzione SQL che potremmo eseguire.

Otterremmo la stessa cosa se rompessimo quella corda in pezzi e usiamo CONCAT per rimontarli insieme per noi, ad es.

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

Possiamo usare quella query come modello per l'istruzione che vogliamo eseguire su information_schema.columns . Sostituiremo 'mydatabase' e 'mytable' con riferimenti a colonne da information_schema.columns tabella che ci fornisce il database e il nome_tabella.

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

Ci sono alcuni database che sicuramente non facciamo voglio aggiornare... mysql , information_schema , performance_schema . O abbiamo bisogno di inserire nella whitelist i database contenenti la tabella che vogliamo aggiornare

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

-o - dobbiamo inserire nella blacklist i database che non vogliamo assolutamente aggiornare

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

Possiamo eseguire quella query (potremmo aggiungere un ORDER BY se vogliamo che le righe restituite in un ordine particolare) e quello che otteniamo è un elenco contenente le istruzioni che vogliamo eseguire. Se salvassimo quel set di stringhe come un file di testo normale (esclusa la riga di intestazione e la formattazione extra), aggiungendo un punto e virgola alla fine di ogni riga, avremmo un file che potremmo eseguire da mysql> client della riga di comando.

(Se qualcuno dei precedenti è fonte di confusione, fatemelo sapere.)

La parte successiva è un po' più complicata. Il resto riguarda un'alternativa al salvataggio dell'output da SELECT come file di testo normale e all'esecuzione delle istruzioni da mysql client della riga di comando.

MySQL fornisce una funzione/funzione che ci consente di eseguire praticamente qualsiasi string come un'istruzione SQL, nel contesto di un programma memorizzato MySQL (ad esempio, una procedura memorizzata. La funzionalità che utilizzeremo si chiama SQL dinamico .

Per utilizzare SQL dinamico , utilizziamo le istruzioni PREPARE , EXECUTE e DEALLOCATE PREPARE . (Il deallocate non è strettamente necessario, MySQL ci ripulirà se non lo usiamo, ma penso che sia buona norma farlo comunque.)

Di nuovo, SQL dinamico è disponibile SOLO nel contesto di un programma memorizzato MySQL. Per fare ciò, abbiamo bisogno di una stringa contenente l'istruzione SQL che vogliamo eseguire. Come semplice esempio, supponiamo di avere questo:

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

Per ottenere il contenuto di str valutata ed eseguita come un'istruzione SQL, lo schema di base è:

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

La prossima parte complicata è metterla insieme alla query che stiamo eseguendo per ottenere il valore della stringa che vogliamo eseguire come istruzioni SQL. Per fare ciò, mettiamo insieme un ciclo del cursore. Lo schema di base per questo è prendere la nostra istruzione SELECT:

SELECT bah FROM humbug

E trasformalo in una definizione di cursore:

DECLARE mycursor FOR SELECT bah FROM humbug ;

Quello che vogliamo è eseguirlo e scorrere le righe che restituisce. Per eseguire l'istruzione e preparare un set di risultati, "apriamo" il cursore

OPEN mycursor; 

Quando avremo finito, emetteremo una "chiusura", per rilasciare il set di risultati, in modo che il server MySQL sappia che non ne abbiamo più bisogno, e può ripulire e liberare le risorse allocate.

CLOSE mycursor;

Ma, prima di chiudere il cursore, vogliamo "fare un ciclo" attraverso il set di risultati, recuperare ogni riga e fare qualcosa con la riga. L'istruzione che utilizziamo per ottenere la riga successiva dal set di risultati in una variabile di procedura è:

FETCH mycursor INTO some_variable;

Prima di poter recuperare le righe nelle variabili, dobbiamo definire le variabili, ad es.

DECLARE some_variable VARCHAR(2000); 

Poiché il nostro cursore (istruzione SELECT) restituisce solo una singola colonna, abbiamo bisogno solo di una variabile. Se avessimo più colonne, avremmo bisogno di una variabile per ogni colonna.

Alla fine, avremo recuperato l'ultima riga dal set di risultati. Quando tentiamo di recuperare il prossimo, MySQL genererà un errore.

Altri linguaggi di programmazione ci permetterebbero di fare un while ciclo, e prendiamo le righe e usciamo dal ciclo quando le abbiamo elaborate tutte. MySQL è più arcano. Per fare un ciclo:

mylabel: LOOP
  -- do something
END LOOP mylabel;

Questo di per sé crea un ciclo infinito molto fine, perché quel ciclo non ha una "uscita". Fortunatamente, MySQL ci fornisce il LEAVE istruzione come un modo per uscire da un ciclo. Di solito non vogliamo uscire dal ciclo la prima volta che lo accediamo, quindi di solito c'è qualche test condizionale che usiamo per determinare se abbiamo finito, e dovremmo uscire dal ciclo, o non abbiamo finito, e dovremmo andare in giro il ciclo di nuovo.

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

Nel nostro caso, vogliamo scorrere tutte le righe nel set di risultati, quindi inseriremo un FETCH a la prima istruzione all'interno del ciclo (il qualcosa di utile che vogliamo fare).

Per ottenere un collegamento tra l'errore che MySQL genera quando tentiamo di recuperare l'ultima riga del set di risultati e il test condizionale dobbiamo determinare se dobbiamo lasciare...

MySQL ci fornisce un modo per definire un CONTINUE HANDLER (qualche istruzione che vogliamo venga eseguita) quando viene generato l'errore...

 DECLARE CONTINUE HANDLER FOR NOT FOUND 

L'azione che vogliamo eseguire è impostare una variabile su TRUE.

 SET done = TRUE;

Prima di poter eseguire SET, dobbiamo definire la variabile:

 DECLARE done TINYINT(1) DEFAULT FALSE;

Con ciò possiamo cambiare il nostro LOOP per verificare se il done variabile è impostata su TRUE, come condizione di uscita, quindi il nostro ciclo è simile a questo:

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

Il "fai qualcosa con la riga" è dove vogliamo prendere il contenuto di some_variable e fare qualcosa di utile con esso. Il nostro cursore ci sta restituendo una stringa che vogliamo eseguire come istruzione SQL. E MySQL ci fornisce l'SQL dinamico funzione che possiamo usare per farlo.

NOTA:MySQL ha regole sull'ordine delle istruzioni nella procedura. Ad esempio il DECLARE dichiarazione deve venire all'inizio. E penso che il CONTINUE HANDLER debba essere l'ultima cosa dichiarata.

Di nuovo:il cursore e SQL dinamico le funzioni sono disponibili SOLO nel contesto di un programma memorizzato MySQL, come una procedura memorizzata. L'esempio che ho dato sopra era solo l'esempio del corpo di una procedura.

Per farlo creare come procedura memorizzata, dovrebbe essere incorporato come parte di qualcosa del genere:

DELIMITER $$

DROP PROCEDURE IF EXISTS myproc $$

CREATE PROCEDURE myproc 
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN

   -- procedure body goes here

END$$

DELIMITER ;

Si spera che questo spieghi l'esempio che ho fornito in modo un po' più dettagliato.