最近在進行源碼的二次開發,突然之間有了一個小的需求,就這麼悄悄的突然出現了。
需求原因就不說了,只說這個怎麼實現的。我是用的是mysql數據庫,其他的略有不同,具體到哪一點就只能具體變化了。
看網上有的說直接把數據庫或者表的編碼做改變就行了,可是這樣對字段來說都沒有影響。想改字段的話,直接對字段進行alter修改。可是把每一個字段寫一遍挺煩人的,於是乎找到了一個語句:
alter table 表名 convert to character set utf8;
可以同時修改這個表的所有字段爲u8,但是想改所有的表難道我要把所有的表名輸入一遍嗎?我那麼懶的人怎麼可能這麼做。就算是花點時間也要找一個一勞永逸的方法。又於是乎,找到了一個查詢所有表名的語句:
select table_name from information_schema.`TABLES` where TABLE_SCHEMA = '數據庫名';
mysql的表信息都保存在了 information_schema 數據庫的 TABLES 表裏面,其中的字段 TABLE_SCHEMA 保存了數據庫名,打開看看就是這樣的:
針對這樣的兩句 sql 進行操作就行了。問題也來了,我得設計一個思路啊。
最先想到的是 把alter語句中表名作爲參數的,而查詢出來的表名作爲變量傳進去,再執行alter語句不就行了嗎?
又於是乎,想到循環這種東西。用到循環的話,那寫個存儲過程吧,在其中聲明變量,弄個循環,查詢的表名賦值給變量,再往alter語句中一傳,美美的。
結果,你妹的,各種報錯!!!!
至於爲什麼,很簡單:我不是太擅長數據庫的操作……,其中的規則也只能一邊做一邊查詢怎麼做。別說,各種百度之後我居然搞了兩種方式實現最初的需求:
A方案: 循環 + 變量賦值 + 動態sql語句拼接執行 和 B方案 :遊標 + 循環 + 變量賦值 + 動態sql語句拼接
沒錯,其中的區別就是有沒有用遊標。可是涉及到的問題就不一樣了。
循環,得有結束條件吧。看看網上的資料,各種條件都是以數字的大小爲條件。當然我的A方案也是這樣的,但是循環體不同。我這查詢的數據中可不涉及數字啊,而且一下子查出來多行,就聲明一個變量(聲明多個也沒用,都是一列對應一個變量)
要麼說知道的多一點總是沒錯的:我每循環一次,不查出來多行,就查出一行,賦給變量去拼接sql執行不就行了。
每次查指定行數,循環時還繼續往下查詢,這不還是專門爲一個關鍵字設計的嘛:limit 。就是它,一般用於分頁查詢所以上面的查詢表名的語句就多了點東西:
select table_name from information_schema.`TABLES` where TABLE_SCHEMA = '數據庫名'limit 起始位置, 每次查詢的數量;
至此我就可以讓聲明的變量被依次賦予所有表的名稱了,到這先把這部分整合下,存儲過程的內容如下:
begin
DECLARE cnt VARCHAR(100); -- 聲明變量用來記錄查詢出的表名
DECLARE i int; -- 循環條件,同時可以用來標記表第幾張表
set i = 0;
-- 循環開始
while i < 32 do -- 這裏是32是因爲我的數據庫中表的數量是32,想不寫死可以通過再定義一個變量,動態賦值
select table_name into @cnt from information_schema.`TABLES` where TABLE_SCHEMA = 'xxx' limit i,1;
select @cnt; -- mysql的打印語句
-- 這裏添加 alter 語句
set i = i + 1;
end while;
-- 循環結束,注意分號
end
稍微說明下,其中的變量 i 循環條件的同時,也可以充當 limit 的起始位置;into 關鍵字是把查詢結果賦值給 cnt 變量;32用變量代替就是:聲明一個變量,比如: num,循環開始前:select count(table_name) into @num from information_schema.`TABLES` where TABLE_SCHEMA = '數據庫名'
這個語句的查詢結果賦值給num,循環條件變成: while i < num do ……即可。
到這可以看到打印出來的該數據庫中的所有表名。那這樣,把之前 alter 語句中的表名替換爲 @cnt 不就完成了嗎?就這樣,我有進坑了。
原因是:標準的 alter 語句中是沒有@這個東西的,而把@去掉的話,它又會認爲我想修改的表名就是 cnt ,而不是變量 cnt 。
所以需要動態拼接sql來執行,完整版
A方案:
begin
DECLARE cnt VARCHAR(100); -- 聲明變量用來記錄查詢出的表名
DECLARE i int; -- 循環條件,同時可以用來標記表第幾張表
set i = 0;
-- 循環開始
while i < 32 do -- 這裏是32是因爲我的數據庫中表的數量是32,想不寫死可以通過再定義一個變量,動態賦值
select table_name into @cnt from information_schema.`TABLES` where TABLE_SCHEMA = '數據庫名' limit i,1;
-- select @cnt; -- mysql的打印語句
-- alter table @cnt convert to character set utf8; -- 這一句報錯,必須動態拼接才行
set @sql = concat("alter table ", @cnt, " convert to character set utf8"); -- 拼接,注意語句中的空格
prepare stmt from @sql; -- 預處理
execute stmt; -- 執行
deallocate prepare stmt; -- 釋放
set i = i + 1;
end while;
-- 循環結束,注意分號
end
反正就是這個意思,而帶有遊標的
B方案:
BEGIN
DECLARE a VARCHAR(100); -- 定義接收遊標數據的變量
DECLARE SQL_FOR_SELECT varchar(500); -- 定義接收遊標數據的變量
DECLARE done INT DEFAULT FALSE; -- 遍歷數據結束標誌
DECLARE cur CURSOR FOR (select table_name from information_schema.`TABLES` where TABLE_SCHEMA = 'demo_survey'); -- 遊標
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 將結束標誌綁定到遊標
-- 打開遊標
OPEN cur;
-- 開始循環(loop循環)
read_loop: LOOP
-- 提取遊標裏的數據,這裏只有一個,多個的話也一樣;
FETCH cur INTO a;
-- 聲明結束的時候
IF done THEN
LEAVE read_loop;
END IF;
-- 要循環的事件,使用了動態sql拼接alter語句,直接寫的話報錯
set SQL_FOR_SELECT = concat("alter table ", a, " convert to character set utf8"); -- 拼接
set @sql = SQL_FOR_SELECT;
prepare stmt from @sql; -- 預處理
execute stmt; -- 執行
deallocate prepare stmt; -- 釋放prepare
END LOOP;
-- 關閉遊標
CLOSE cur;
END
B方案中用了 loop 循環,這樣我可以學習下不同循環的使用方法嘛。對比兩種方案可以看出來循環條件的不同,而且由於遊標的特性,B方案中的查詢結果不需要limit限制
兩種方案的思路是一樣的,手段不同而已。
當然了,我這個最初的需求很少遇到。
不過裏面用到的東西很多我之前是沒怎麼用過的,所以記錄下。
總結:
1.一次性修改表中所有字段的字符集語句:alter table `表名` convert to character set utf8;
2.查詢某個數據庫中所有表的信息語句:select * from information_schema.`TABLES` where TABLE_SCHEMA = '該數據庫名';
3.查詢結果賦值給變量可以用 into 關鍵字(也有其他的)。遊標中沒有@
4.變量賦值可以不用先聲明,如A方案中的@sql ,直接使用 set @sql = xxx。
5.存儲過程中可以直接使用 alter語句 ,也就是靜態sql語句。但是需要傳遞參數的話,要使用動態sql拼接來執行(小慢,就循環了30多次,就能感覺出來比靜態的慢,可能是因爲我循環的是ddl語句吧)。
6.循環,遊標,動態sql用完之後,都有個結束的語句和分號
LG