Come posso scorrere tutte le righe di una tabella? (MySQL)


90

Ho una tabella A e c'è un ID chiave primaria.

Ora voglio passare attraverso tutte le righe in A.

Ho trovato qualcosa come "per ogni record in LA", ma sembra che non sia così che lo fai in MySQL.

Il fatto è che per ogni riga voglio prendere un campo e trasformarlo, inserirlo in un'altra tabella e quindi aggiornare alcuni dei campi della riga. Posso mettere la parte select e l'inserto in un'unica istruzione, ma non so anche come ottenere l'aggiornamento. Quindi voglio fare il loop. E per esercitarmi non voglio usare nient'altro che MySQL.

modificare

Apprezzerei un esempio.

E una soluzione che non ha bisogno di essere inserita in una procedura.

modifica 2

ok pensa a questo scenario:

Tabella A e B, ciascuna con i campi ID e VAL.

Ora questo è lo pseudo codice per quello che voglio fare:

for(each row in A as rowA)
{
  insert into B(ID, VAL) values(rowA[ID], rowA[VAL]);
}

fondamentalmente copiare il contenuto di A in B usando un ciclo.

(questo è solo un esempio semplificato, ovviamente non useresti un ciclo per questo.)}


Ciao Rafeel per me il suo errore come: My SQL> for (each row in wp_mobiune_ecommune_user as x) {insert into wp_mobiune_ecommune_user (gender_new) values ​​(x [gender])}; RROR 1064 (42000): Hai un errore nella sintassi SQL; controlla il manuale che corrisponde alla versione del tuo server MySQL per la sintassi corretta da usare vicino a 'for (ogni riga in wp_mobiune_ecommune_user come x) {inserire in wp_mobiune_ecommune' alla riga 1
dinesh kandpal

Risposte:


137

Poiché il suggerimento di un ciclo implica la richiesta di una soluzione di tipo procedura. Ecco il mio.

Qualsiasi query che funziona su un singolo record preso da una tabella può essere racchiusa in una procedura per farla scorrere su ogni riga di una tabella in questo modo:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Quindi ecco la procedura come nel tuo esempio (table_A e table_B usati per chiarezza)

CREATE PROCEDURE ROWPERROW()
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
SELECT COUNT(*) FROM table_A INTO n;
SET i=0;
WHILE i<n DO 
  INSERT INTO table_B(ID, VAL) SELECT (ID, VAL) FROM table_A LIMIT i,1;
  SET i = i + 1;
END WHILE;
End;
;;

Quindi non dimenticare di reimpostare il delimitatore

DELIMITER ;

Ed esegui la nuova procedura

CALL ROWPERROW();

Puoi fare quello che vuoi nella riga "INSERT INTO" che ho semplicemente copiato dalla tua richiesta di esempio.

Notare ATTENTAMENTE che la riga "INSERT INTO" utilizzata qui rispecchia la riga nella domanda. In base ai commenti a questa risposta, è necessario assicurarsi che la query sia sintatticamente corretta per quale versione di SQL si sta eseguendo.

Nel semplice caso in cui il tuo campo ID è incrementato e inizia da 1 la riga nell'esempio potrebbe diventare:

INSERT INTO table_B(ID, VAL) VALUES(ID, VAL) FROM table_A WHERE ID=i;

Sostituendo la riga "SELECT COUNT" con

SET n=10;

Ti consentirà di testare la tua query solo sui primi 10 record nella tabella_A.

Un'ultima cosa. Questo processo è anche molto facile da annidare su tabelle diverse ed era l'unico modo in cui ho potuto eseguire un processo su una tabella che inseriva dinamicamente diversi numeri di record in una nuova tabella da ogni riga di una tabella padre.

Se hai bisogno che funzioni più velocemente, prova a impostarlo in base, altrimenti va bene. È anche possibile riscrivere quanto sopra sotto forma di cursore, ma potrebbe non migliorare le prestazioni. per esempio:

DROP PROCEDURE IF EXISTS cursor_ROWPERROW;
DELIMITER ;;

CREATE PROCEDURE cursor_ROWPERROW()
BEGIN
  DECLARE cursor_ID INT;
  DECLARE cursor_VAL VARCHAR;
  DECLARE done INT DEFAULT FALSE;
  DECLARE cursor_i CURSOR FOR SELECT ID,VAL FROM table_A;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
  OPEN cursor_i;
  read_loop: LOOP
    FETCH cursor_i INTO cursor_ID, cursor_VAL;
    IF done THEN
      LEAVE read_loop;
    END IF;
    INSERT INTO table_B(ID, VAL) VALUES(cursor_ID, cursor_VAL);
  END LOOP;
  CLOSE cursor_i;
END;
;;

Ricorda di dichiarare le variabili che utilizzerai dello stesso tipo di quelle delle tabelle interrogate.

Il mio consiglio è di andare con le query basate su set quando è possibile e utilizzare solo cicli o cursori semplici se necessario.


2
INSERT INTO table_B (ID, VAL) VALUES (ID, VAL) FROM table_A LIMIT i, 1; dà un errore di sintassi.
Jonathan

1
Sembra mancare "DICHIARARE FATTO INT DEFAULT FALSE;" dopo la dichiarazione di cursor_i
ErikL

1
Dov'è la proprietà done impostata su true, dovrei posizionarla o è una parola riservata che viene utilizzata automaticamente dal cursore mysql?
IgniteCoders

1
La chiamata INSERT INTO genera un errore di sintassi a partire da SQL 5.5, la chiamata corretta è INSERT INTO table_B (ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID = i;
Ambar

Questo richiederà una vita, quindi prova ad aumentare la dimensione del batch a 1000 modificando il limite e incrementando: LIMIT i,1000;e set i = i + 1000;
Alan Deep

16

Dovresti davvero usare una soluzione basata su set che coinvolge due query (inserimento di base):

INSERT INTO TableB (Id2Column, Column33, Column44)
SELECT id, column1, column2 FROM TableA

UPDATE TableA SET column1 = column2 * column3

E per la tua trasformazione:

INSERT INTO TableB (Id2Column, Column33, Column44)
SELECT 
    id, 
    column1 * column4 * 100, 
    (column2 / column12) 
FROM TableA

UPDATE TableA SET column1 = column2 * column3

Ora se la tua trasformazione è più complicata di così e ha coinvolto più tabelle, pubblica un'altra domanda con i dettagli.


+1 per l'esempio. anche se dubito che questo sia applicabile nella mia situazione b / c l'aggiornamento successivo è correlato all'inserimento e in particolare alle righe selezionate causa quale inserimento specifico. Questo è il motivo per cui ho suggerito un ciclo, quindi posso eseguire la selezione / inserimento / aggiornamento per ciascuna riga individualmente.
Raffael

2
@ Raffael1984: modifica la tua domanda per aggiungere le condizioni per "righe specifiche che causano inserimenti specifici" e noi possiamo aiutarti. Davvero non vuoi andare sul percorso del cursore / loop: è estremamente inefficiente.
Raj More

vabbè ... sai che sarei più che felice di passare alla modalità di query basata su set! ma l'efficienza non è una motivazione qui poiché la mia domanda è più di interesse accademico. il tavolo è abbastanza piccolo da poterlo fare a mano. Apprezzerei davvero una versione in loop. Potrei pubblicare di nuovo il problema fornendo maggiori dettagli per consentire a qualcuno di aiutarmi con uno stile basato sul set.
Raffael

d'accordo con @RajMore, il cursore / loop è inefficiente, di seguito sono 2 i collegamenti con le prestazioni del cursore per i riferimenti: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ ...
Browny Lin

@RajMore Puoi aiutarmi nella mia domanda
Nurav

4

I CURSORI sono un'opzione qui, ma generalmente disapprovati poiché spesso non fanno un uso migliore del motore di query. Considera la possibilità di esaminare le "query basate su SET" per vedere se puoi ottenere ciò che desideri senza utilizzare un CURSORE.


Non riesco a trovare molte informazioni sulle query basate su set. ma immagino che tu intenda una scelta in un tipo di dichiarazione. il problema è che devo anche eseguire un aggiornamento.
Raffael

0

L'esempio di Mr Purple che ho usato in mysql trigger come quello,

begin
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
Select COUNT(*) from user where deleted_at is null INTO n;
SET i=0;
WHILE i<n DO 
  INSERT INTO user_notification(notification_id,status,userId)values(new.notification_id,1,(Select userId FROM user LIMIT i,1)) ;
  SET i = i + 1;
END WHILE;
end

2
Puoi aggiungere alcune informazioni su come funziona la tua soluzione?
TheTanic

-26
    Use this:

    $stmt = $user->runQuery("SELECT * FROM tbl WHERE ID=:id");
    $stmt->bindparam(":id",$id);
    $stmt->execute();

        $stmt->bindColumn("a_b",$xx);
        $stmt->bindColumn("c_d",$yy);


    while($rows = $stmt->fetch(PDO::FETCH_BOUND))
    {
        //---insert into new tble
    }   
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.