Wie kann ich alle Zeilen einer Tabelle durchlaufen? (MySQL)

90

Ich habe eine Tabelle A und es gibt eine Primärschlüssel-ID.

Jetzt möchte ich alle Zeilen in A durchgehen.

Ich habe so etwas wie "für jeden Datensatz in A" gefunden, aber so scheint es in MySQL nicht zu sein.

Für jede Zeile möchte ich ein Feld nehmen und transformieren, es in eine andere Tabelle einfügen und dann einige Felder der Zeile aktualisieren. Ich kann den ausgewählten Teil und die Einfügung in eine Anweisung einfügen, aber ich weiß auch nicht, wie ich das Update dort einfügen soll. Also möchte ich eine Schleife machen. Und zum Üben möchte ich nichts anderes als MySQL verwenden.

bearbeiten

Ich würde mich über ein Beispiel freuen.

Und eine Lösung, die nicht in ein Verfahren einbezogen werden muss.

bearbeiten 2

Okay, denken Sie an dieses Szenario:

Tabelle A und B mit den Feldern ID und VAL.

Dies ist der Pseudocode für das, was ich tun möchte:

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

Grundsätzlich wird der Inhalt von A mit einer Schleife nach B kopiert.

(Dies ist nur ein vereinfachtes Beispiel, dafür würden Sie natürlich keine Schleife verwenden.)}

Raffael
quelle
Hallo Rafeel für mich, es gibt folgenden Fehler: My SQL> for (jede Zeile in wp_mobiune_ecommune_user als x) {in wp_mobiune_ecommune_user (gender_new) -Werte einfügen (x [gender])}; RROR 1064 (42000): Sie haben einen Fehler in Ihrer SQL-Syntax. Überprüfen Sie das Handbuch, das Ihrer MySQL-Serverversion entspricht, auf die richtige Syntax, die in der Nähe von 'für (jede Zeile in wp_mobiune_ecommune_user als x) {in wp_mobiune_ecommune einfügen' in Zeile 1
dinesh kandpal

Antworten:

135

Da der Vorschlag einer Schleife die Anforderung einer Lösung vom Prozedurtyp impliziert. Hier ist mein.

Jede Abfrage, die mit einem einzelnen Datensatz aus einer Tabelle funktioniert, kann in eine Prozedur eingeschlossen werden, damit sie wie folgt durch jede Zeile einer Tabelle läuft:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Dann ist hier die Prozedur gemäß Ihrem Beispiel (Tabelle_A und Tabelle_B zur Verdeutlichung verwendet)

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;
;;

Vergessen Sie dann nicht, das Trennzeichen zurückzusetzen

DELIMITER ;

Führen Sie die neue Prozedur aus

CALL ROWPERROW();

In der Zeile "INSERT INTO", die ich einfach aus Ihrer Beispielanfrage kopiert habe, können Sie tun, was Sie möchten.

Beachten Sie sorgfältig, dass die hier verwendete Zeile "INSERT INTO" die Zeile in der Frage widerspiegelt. Gemäß den Kommentaren zu dieser Antwort müssen Sie sicherstellen, dass Ihre Abfrage für jede von Ihnen ausgeführte SQL-Version syntaktisch korrekt ist.

In dem einfachen Fall, in dem Ihr ID-Feld inkrementiert ist und bei 1 beginnt, könnte die Zeile im Beispiel wie folgt lauten:

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

Ersetzen der Zeile "SELECT COUNT" durch

SET n=10;

Ermöglicht es Ihnen, Ihre Abfrage nur für die ersten 10 Datensätze in table_A zu testen.

Eine letzte Sache. Dieser Prozess ist auch sehr einfach in verschiedene Tabellen zu verschachteln und war die einzige Möglichkeit, einen Prozess für eine Tabelle auszuführen, bei dem aus jeder Zeile einer übergeordneten Tabelle dynamisch unterschiedliche Anzahlen von Datensätzen in eine neue Tabelle eingefügt wurden.

Wenn Sie es benötigen, um schneller zu laufen, versuchen Sie auf jeden Fall, es set-basiert zu machen. Wenn nicht, ist dies in Ordnung. Sie können das Obige auch in Cursorform umschreiben, aber es kann die Leistung nicht verbessern. z.B:

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;
;;

Denken Sie daran, die Variablen, die Sie verwenden, als denselben Typ wie die aus den abgefragten Tabellen zu deklarieren.

Mein Rat ist, mit setbasierten Abfragen zu arbeiten, wenn Sie können, und nur einfache Schleifen oder Cursor zu verwenden, wenn Sie müssen.

Herr Lila
quelle
2
INSERT INTO table_B (ID, VAL) VALUES (ID, VAL) FROM table_A LIMIT i, 1; gibt einen Syntaxfehler aus.
Jonathan
1
Scheint zu fehlen 'DECLARE done INT DEFAULT FALSE;' nach cursor_i Deklaration
ErikL
1
Wo wird die Eigenschaft auf true gesetzt, ich sollte sie platzieren oder ist ein reserviertes Wort, das automatisch vom MySQL-Cursor verwendet wird?
IgniteCoders
1
Der Aufruf INSERT INTO löst ab SQL 5.5 einen Syntaxfehler aus. Der richtige Aufruf lautet INSERT INTO table_B (ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID = i;
Ambar
Dies dauert ein Leben lang. Versuchen Sie daher, die Stapelgröße auf 1000 zu erhöhen, indem Sie den Grenzwert und das Inkrement ändern: LIMIT i,1000;und set i = i + 1000;
Alan Deep
16

Sie sollten wirklich eine satzbasierte Lösung verwenden, die zwei Abfragen umfasst (Grundeinfügung):

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

UPDATE TableA SET column1 = column2 * column3

Und für deine Transformation:

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

UPDATE TableA SET column1 = column2 * column3

Wenn Ihre Transformation komplizierter ist und mehrere Tabellen umfasst, stellen Sie eine weitere Frage mit den Details.

Raj Mehr
quelle
+1 für das Beispiel. Obwohl ich bezweifle, dass dies in meiner Situation b / c zutrifft, bezieht sich die anschließende Aktualisierung auf die Einfügung und insbesondere darauf, welche ausgewählten Zeilen welche spezifische Einfügung verursachen. Aus diesem Grund habe ich eine Schleife vorgeschlagen, damit ich für jede Zeile einzeln auswählen / einfügen / aktualisieren kann.
Raffael
2
@ Raffael1984: Bearbeiten Sie Ihre Frage, um die Bedingungen für "bestimmte Zeilen, die bestimmte Einfügungen verursachen" hinzuzufügen, und wir können Ihnen dabei helfen. Sie möchten wirklich nicht die Cursor / Loop-Route gehen - sie ist äußerst ineffizient.
Raj More
na ja ... du weißt, ich würde mehr als glücklich sein, den satzbasierten Abfrageweg zu gehen! Effizienz ist hier jedoch keine Motivation, da meine Frage eher von akademischem Interesse ist. Der Tisch ist klein genug, dass ich es von Hand machen könnte. Ich würde mich sehr über eine Loop-Version freuen. Ich könnte dieses Problem erneut posten und weitere Details bereitstellen, damit mir jemand beim satzbasierten Stil helfen kann.
Raffael
stimme @RajMore zu, Cursor / Schleife ist ineffizient, unten sind 2 Links zur Cursorleistung als Referenz aufgeführt: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ …
Browny Lin
@ RajMore Kannst du mir bei meiner Frage
Nurav
4

CURSOREN sind hier eine Option, werden jedoch im Allgemeinen verpönt, da sie die Abfrage-Engine häufig nicht optimal nutzen. Untersuchen Sie "SET-basierte Abfragen", um festzustellen, ob Sie ohne Verwendung eines CURSOR das erreichen können, was Sie möchten.

Ron Weston
quelle
Ich kann nicht viele Informationen zu satzbasierten Abfragen finden. aber ich denke du meinst eine Auswahl in eine Art Aussage. Das Problem ist, dass ich auch ein Update ausführen lassen muss.
Raffael
0

Das Beispiel von Herrn Purple, das ich so in MySQL-Trigger verwendet habe,

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
Erkan RUA
quelle
2
Können Sie bitte einige Informationen hinzufügen, wie Ihre Lösung funktioniert?
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
    }   
utee
quelle