【SQL】 藉助遊標來實現文字的分列與合併
有時我們會遇到需要把表中個別欄位拆分成多條資料或是把多條資料合併到一起的情況。一般的程式語言都有函式“split”和“join”來實現,而SQL中既沒有這些函式也沒有類似陣列和列表這類方便儲存成組資料的資料型別,一些對於字串的處理功能實現起來比較麻煩。直到SQL Server 2016才新增了string_split函式,專門用來拆分字串。但在此之前的版本,我們只能通過其他方式來實現這些功能。
一、文字分列的實現
現有測試資料表如下圖,“學科”和“成績”都是多條資料通過逗號連線的。如果想把他們拆分出多條資料,讓每門學科和成績都對應一條記錄,自然需要把“學科”與“成績”兩列資料中按照逗號作為分隔符進行拆分。
- 利用xml對字串進行拆分
在網上搜索的比較簡便和常用的方法是利用xml對字串進行拆分,在此不再細說,程式碼如下:
1 declare @x xml,@str nvarchar(200) 2 set @str='aaa,bbb,ccc' 3 SELECT @x = CONVERT(xml, 4 '<v>' + REPLACE(@str, ',', '</v><v>') + '</v>') 5 SELECT N.v.value('.', 'varchar(100)') 6 FROM @x.nodes('/v') N(v)
- 利用遊標配合函式substring來實現表中文字的分列
思路:通過建立遊標逐條獲取表內資訊,並對此進行加工,使用函式substring分段讀取“學科”和“成績”,將結果插入到另外一個新建立的表“test_split”中。
實現方法:建立一個新表"test_split",通過建立遊標逐條獲取表“test”內資訊,針對從遊標中獲取的每一條記錄,進行如下的操作。因為“學科”與“成績”都是通過逗號進行連線的一組資料,所以分別用函式charindex定位到“學科”與“成績”中逗號第一次出現的位置,隨後使用函式substring擷取從開頭(也就是索引號1的位置)到逗號第一次出現位置(通過charindex
程式碼與實現效果如下:
1 if exists(select name from sys.tables where name='test_split') 2 drop table test_split 3 CREATE TABLE test_split(姓名 nvarchar(50),學科 nvarchar(200),成績 nvarchar(200)) 4 go 5 6 DECLARE MyCursor CURSOR 7 for select * FROM dbo.test 8 open MyCursor 9 DECLARE @姓名 nvarchar(50),@學科 nvarchar(200),@成績 nvarchar(200),@學科Temp nvarchar(200),@成績Temp nvarchar(200) 10 declare @getindex1 int,@getindex2 int 11 FETCH NEXT FROM MyCursor INTO @姓名,@學科,@成績 12 WHILE @@FETCH_STATUS =0 13 BEGIN 14 set @getindex1=charindex(',',@學科) 15 set @getindex2=charindex(',',@成績) 16 while(@getindex1<>0) 17 begin 18 set @學科Temp=substring(@學科,1,@getIndex1-1) 19 set @成績Temp=substring(@成績,1,@getIndex2-1) 20 insert into test_split values (@姓名,@學科Temp,@成績Temp) 21 set @學科=stuff(@學科,1,@getindex1,'') 22 set @成績=stuff(@成績,1,@getindex2,'') 23 set @getindex1=charindex(',',@學科) 24 set @getindex2=charindex(',',@成績) 25 end 26 insert into test_split values (@姓名,@學科,@成績) 27 FETCH NEXT FROM MyCursor INTO @姓名,@學科,@成績 28 END 29 30 CLOSE MyCursor 31 DEALLOCATE MyCursor
原表“test"中的四條記錄給拆分成了對應的9條記錄。
二、文字合併的實現
與文字的分列對應的,可以採用同樣的方法來實現文字的合併。下面我們來嘗試將之前生成的表“test_split”重新合併成新表“test_join”,實現欄位“學科”與“成績”的合併。
思路:通過建立遊標逐條獲取表內資訊,設定一個變數“@姓名Temp”,將每次獲取到的資料中的“姓名”與該變數進行對比,相同的就將其中的“學科”與“成績”進行合併。
實現方法:建立一個新表“test_join”,同時建立一個遊標來讀取"test_split"中的資料(表中資料需要按照“姓名”進行排序),將每次遊標獲取到的資料中的“姓名”與“@姓名Temp”對比,如果相同就把“學科”與“成績”累加到變數“@學科”與“@成績”中,並使用逗號分隔;如果不同,就將變數值插入到新建立的表“test_join”中,同時使用新獲取到的“姓名”、“學科“、”成績“更新變數”@姓名”、“@學科”、“@成績”。
程式碼及實現效果如下:
1 if exists(select name from sys.tables where name='test_join') 2 drop table test_join 3 CREATE TABLE test_join(姓名 nvarchar(50),學科 nvarchar(200),成績 nvarchar(200)); 4 go 5 6 DECLARE MyCursor CURSOR 7 for select 姓名,學科,成績 FROM dbo.test_split 8 open MyCursor 9 DECLARE @姓名 nvarchar(50),@學科 nvarchar(200),@成績 nvarchar(200) 10 declare @姓名Temp nvarchar(50),@學科Temp nvarchar(200),@成績Temp nvarchar(200) 11 set @姓名Temp = '' 12 FETCH NEXT FROM MyCursor INTO @姓名,@學科,@成績 13 14 WHILE @@FETCH_STATUS =0 15 BEGIN 16 if @姓名 <> @姓名Temp 17 begin 18 insert into test_join values (@姓名Temp,@學科Temp,@成績Temp) 19 set @姓名Temp=@姓名 20 set @學科Temp=@學科 21 set @成績Temp=@成績 22 end 23 else 24 begin 25 set @學科Temp=@學科Temp+','+@學科 26 set @成績Temp=@成績Temp+','+@成績 27 end 28 FETCH NEXT FROM MyCursor INTO @姓名,@學科,@成績 29 END 30 if @姓名 = @姓名Temp 31 insert into test_join values (@姓名Temp,@學科Temp,@成績Temp) 32 33 CLOSE MyCursor 34 DEALLOCATE MyCursor
從最終的執行結果可以看到,這裡還存在一個小問題,就是因為一開始把變數“@姓名”初始化成空值,所以在新表"test_join"中會產生一條無用記錄,但為了程式碼中迴圈體的條件判斷,沒有太好的消除辦法。