Bagaimana cara melakukan loop melalui semua baris tabel? (MySQL)

90

Saya memiliki tabel A dan ada satu ID kunci utama.

Sekarang saya ingin menelusuri semua baris di A.

Saya menemukan sesuatu seperti 'untuk setiap catatan di A', tetapi tampaknya ini bukan cara Anda melakukannya di MySQL.

Masalahnya adalah untuk setiap baris saya ingin mengambil bidang dan mengubahnya, memasukkannya ke dalam tabel lain dan kemudian memperbarui beberapa bidang baris. Saya dapat memasukkan bagian yang dipilih dan menyisipkan ke dalam satu pernyataan, tetapi saya juga tidak tahu cara mendapatkan pembaruan di sana. Jadi saya ingin mengulang. Dan untuk latihan saya tidak ingin menggunakan yang lain selain MySQL.

edit

Saya sangat menghargai contoh.

Dan solusi yang tidak perlu dimasukkan ke dalam prosedur.

edit 2

oke pikirkan skenario ini:

Tabel A dan B, masing-masing dengan kolom ID dan VAL.

Sekarang ini adalah kode pseudo untuk apa yang ingin saya lakukan:

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

pada dasarnya menyalin konten A ke B menggunakan loop.

(ini hanya contoh yang disederhanakan, tentu saja Anda tidak akan menggunakan loop untuk ini.)}

Raffael
sumber
Hai Rafeel bagi saya yang memberikan kesalahan sebagai: SQL saya> untuk (setiap baris di wp_mobiune_ecommune_user sebagai x) {masukkan ke dalam nilai wp_mobiune_ecommune_user (gender_new) (x [gender])}; RROR 1064 (42000): Anda mengalami kesalahan dalam sintaks SQL; periksa manual yang sesuai dengan versi server MySQL Anda untuk sintaks yang tepat untuk digunakan near 'for (setiap baris di wp_mobiune_ecommune_user sebagai x) {masukkan ke wp_mobiune_ecommune' di baris 1
dinesh kandpal

Jawaban:

137

Karena saran perulangan menyiratkan permintaan untuk solusi tipe prosedur. Ini milikku.

Kueri apa pun yang bekerja pada satu rekaman yang diambil dari tabel bisa dibungkus dalam prosedur untuk membuatnya berjalan melalui setiap baris tabel seperti:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Lalu inilah prosedur sesuai contoh Anda (table_A dan table_B digunakan untuk kejelasan)

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

Kemudian jangan lupa untuk mengatur ulang pembatas

DELIMITER ;

Dan jalankan prosedur baru

CALL ROWPERROW();

Anda dapat melakukan apapun yang Anda suka di baris "INSERT INTO" yang saya salin dari permintaan contoh Anda.

Perhatikan dengan HATI-HATI bahwa baris "SISIPKAN KE" yang digunakan di sini mencerminkan baris dalam pertanyaan. Sesuai dengan komentar untuk jawaban ini, Anda perlu memastikan bahwa kueri Anda benar secara sintaksis untuk versi SQL mana pun yang Anda jalankan.

Dalam kasus sederhana di mana bidang ID Anda bertambah dan dimulai pada 1, baris dalam contoh dapat menjadi:

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

Mengganti baris "SELECT COUNT" dengan

SET n=10;

Akan membiarkan Anda menguji kueri Anda pada 10 catatan pertama di table_A saja.

Satu hal terakhir. Proses ini juga sangat mudah untuk disarangkan di tabel yang berbeda dan merupakan satu-satunya cara saya dapat melakukan proses pada satu tabel yang secara dinamis memasukkan jumlah record yang berbeda ke dalam tabel baru dari setiap baris tabel induk.

Jika Anda membutuhkannya untuk berjalan lebih cepat maka pastikan untuk membuatnya berdasarkan, jika tidak maka ini bagus. Anda juga dapat menulis ulang di atas dalam bentuk kursor tetapi mungkin tidak meningkatkan kinerja. misalnya:

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

Ingatlah untuk mendeklarasikan variabel yang akan Anda gunakan sebagai jenis yang sama dengan yang ada di tabel yang ditanyakan.

Saran saya adalah menggunakan kueri berbasis set ketika Anda bisa, dan hanya gunakan loop atau kursor sederhana jika perlu.

Tuan Ungu
sumber
2
MASUKKAN KE tabel_B (ID, VAL) NILAI (ID, VAL) DARI tabel_A BATAS i, 1; memberikan kesalahan sintaks.
Jonathan
1
Tampaknya hilang 'MENYATAKAN selesai INT DEFAULT FALSE;' setelah deklarasi cursor_i
ErikL
1
Di mana properti done disetel ke true, saya harus meletakkannya atau kata yang dicadangkan yang digunakan secara otomatis oleh kursor mysql?
IgniteCoders
1
Panggilan INSERT INTO memunculkan kesalahan sintaks pada SQL 5.5, panggilan yang benar adalah INSERT INTO table_B (ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID = i;
Ambar
Ini akan memakan waktu seumur hidup, jadi cobalah untuk meningkatkan ukuran tumpukan menjadi 1000 dengan mengubah batas dan kenaikan: LIMIT i,1000;dan set i = i + 1000;
Alan Deep
16

Anda harus benar-benar menggunakan solusi berbasis set yang melibatkan dua kueri (penyisipan dasar):

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

UPDATE TableA SET column1 = column2 * column3

Dan untuk transformasi Anda:

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

UPDATE TableA SET column1 = column2 * column3

Sekarang jika transformasi Anda lebih rumit dari itu dan melibatkan banyak tabel, posting pertanyaan lain dengan detailnya.

Raj Lebih
sumber
+1 sebagai contoh. Meskipun saya ragu ini berlaku di situ saya b / c pembaruan setelah itu terkait dengan penyisipan dan terutama baris yang dipilih menyebabkan penyisipan tertentu. Inilah sebabnya saya menyarankan loop, sehingga saya dapat melakukan seleksi / sisipkan / perbarui untuk setiap baris satu per satu.
Raffael
2
@ Raffael1984: edit pertanyaan Anda untuk menambahkan ketentuan untuk "baris tertentu menyebabkan sisipan tertentu" dan kami dapat membantu untuk itu. Anda benar-benar tidak ingin pergi ke kursor / rute loop - ini sangat tidak efisien.
Raj More
oh baiklah ... Anda tahu saya akan sangat senang untuk menggunakan cara kueri berbasis set! tetapi efisiensi bukanlah motivasi di sini karena pertanyaan saya lebih pada kepentingan akademis. mejanya cukup kecil saya bisa melakukannya dengan tangan. Saya akan sangat menghargai versi loop. Saya mungkin memposting masalah itu lagi dengan memberikan detail lebih lanjut untuk memungkinkan seseorang membantu saya dengan gaya berbasis set.
Raffael
setuju dengan @RajMore, cursor / loop tidak efisien, berikut 2 link tentang performa kursor untuk referensi: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ …
Browny Lin
@RajMore Dapatkah Anda membantu saya dalam pertanyaan
Nurav
4

KURSOR adalah opsi di sini, tetapi umumnya tidak disukai karena sering kali tidak memanfaatkan mesin kueri dengan baik. Pertimbangkan untuk menyelidiki 'SET Based Queries' untuk melihat apakah Anda dapat mencapai apa yang ingin Anda lakukan tanpa menggunakan CURSOR.

Ron Weston
sumber
Saya tidak dapat menemukan banyak informasi tentang kueri berbasis kumpulan. tapi saya rasa yang Anda maksud adalah memilih jenis pernyataan. masalahnya adalah saya juga harus menjalankan pembaruan.
Raffael
0

Contoh Pak Ungu yang saya gunakan di pemicu mysql seperti itu,

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
sumber
2
Bisakah Anda menambahkan beberapa informasi bagaimana solusi Anda bekerja?
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
sumber