【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,因为我们不需要那个逗号)的部分,将获取到的新字符串作为新表“test_split”中的“学科”与“成绩”连同“姓名”一起插入到新表“test_split”中。再将原字符串从开头到第一个逗号的部分删除作为新的字符串循环重复前面的操作,直到字符串中没有逗号为止。这样我们就把原表“test”中原本的一条数据根据“学科”与“成绩”的内容拆分成了多条数据。
代码与实现效果如下:
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 31 insert into test_join values (@姓名Temp,@学科Temp,@成绩Temp) 32 33 CLOSE MyCursor 34 DEALLOCATE MyCursor
从最终的运行结果可以看到,这里还存在一个小问题,就是因为一开始把变量“@姓名”初始化成空值,所以在新表"test_join"中会产生一条无用记录,但为了代码中循环体的条件判断,没有太好的消除办法。