SQLServer查询多行转换为一行的合并问题(FOR XML PATH应用)
以下主讲sql多行转为一行的合并问题,并在sql2000和2005得到验证
问题描述:将如下形式的数据按id字段合并value字段。

id    value
 —– ——
 1     aa
 1     bb
 2     aaa
 2     bbb
 2     ccc
 需要得到结果:
 id     value
 —— ———–
 1      aa,bb
 2      aaa,bbb,ccc
 即:group by id, 求 value 的和(字符串相加)

定义表:

create table tb(id int, value varchar(10))
 insert into tb values(1, ‘aa’)
 insert into tb values(1, ‘bb’)
 insert into tb values(2, ‘aaa’)
 insert into tb values(2, ‘bbb’)
 insert into tb values(2, ‘ccc’)
 go
 一、sql2000中要用自定义的函数解决
 create function dbo.f_str(@id int) returns varchar(100)
 as
 begin
     declare @str varchar(1000)
     set @str = ”
     select @str = @str + ‘,’ + cast(value as varchar) from tb where id = @id
     set @str = right(@str , len(@str) – 1)
     return @str
 end
 go


–调用函数

select id , value = dbo.f_str(id) from tb group by id
 –drop function dbo.f_str
 –drop table tb


二、sql2005中可以用FOR XML PATH语句来实现
大家都知道在SQL Server中利用 FOR XML PATH 语句能够把查询的数据生成XML数据,下面是它的一些应用示例。

DECLARE @TempTable table(UserID int , UserName nvarchar(50));
 insert into @TempTable (UserID,UserName) values (1,’a')
 insert into @TempTable (UserID,UserName) values (2,’b')
 select UserID,UserName from @TempTable FOR XML PATH
 运行这段脚本,将生成如下结果:
 <row>
   <UserID>1</UserID>
   <UserName>a</UserName>
 </row>
 <row>
   <UserID>2</UserID>
   <UserName>b</UserName>
 </row>


可以看到两行数据生成了两个节点,修改一下PATH的参数:

select UserID,UserName from @TempTable FOR XML PATH(‘test’)
 运行上述脚本,将生成如下的结果:
 <test>
   <UserID>1</UserID>
   <UserName>a</UserName>
 </test>
 <test>
   <UserID>2</UserID>
   <UserName>b</UserName>
 </test>


其实PATH() 括号内的参数是控制节点名称的,那如果是空字符串(不是没有参数)会是什么结果呢?

select UserID,UserName from @TempTable FOR XML PATH(”)
 执行上面这段脚本将生成结果:
 <UserID>1</UserID>
 <UserName>a</UserName>
 <UserID>2</UserID>
 <UserName>b</UserName>
 这样就不显示上级节点了,在PATH模式中,列名或列别名被作为XPath表达式来处理,也就是说,是列的名字,试一下不给指定列名和别名会是怎么样?
 select CAST(UserID AS varchar) + ”,UserName + ” from @TempTable FOR XML PATH(”)
 运行上面这句将生成结果
 1a2b
 所有数据都生成一行,而且没有连接字符,还可以再变化一下:
 select CAST(UserID AS varchar) + ‘,’,UserName + ”,’;’ from @TempTable FOR XML PATH(”)
 生成结果
 1,a;2,b;
 也就是说可以通过控制参数来生成自己想要的结果,例如:
 select ‘{‘ + CAST(UserID AS varchar) + ‘,’,'”‘ +UserName + ‘”‘,’}’ from @TempTable FOR XML PATH(”)
 生成结果
 {1,”a”}{2,”b”}
 还可以生成其他格式,大家可以根据自己需要的格式进行组合。
 下面是一个数据统计的应用,希望大家可以通过下面的实例想到更多的应用
 DECLARE @T1 table(UserID int , UserName nvarchar(50),CityName nvarchar(50));
 insert into @T1 (UserID,UserName,CityName) values (1,’a',’上海’)
 insert into @T1 (UserID,UserName,CityName) values (2,’b',’北京’)
 insert into @T1 (UserID,UserName,CityName) values (3,’c',’上海’)
 insert into @T1 (UserID,UserName,CityName) values (4,’d',’北京’)
 insert into @T1 (UserID,UserName,CityName) values (5,’e',’上海’)
 SELECT B.CityName,LEFT(UserList,LEN(UserList)-1) FROM (
 SELECT CityName,
     (SELECT UserName+’,’ FROM @T1 WHERE CityName=A.CityName  FOR XML PATH(”)) AS UserList
 FROM @T1 A
 GROUP BY CityName
 ) B
 生成结果(每个城市的用户名)
 北京 b,d
 上海 a,c,e
 回到上面的例子就是执行如下语句:
 select id, [value] = stuff((select ‘,’ + [value] from tb t where id = tb.id for xml path(”)) , 1 , 1 , ”)
 from tb
 group by id
 –drop table tb
 (STUFF函数用法
 1、作用:删除指定长度的字符,并在指定的起点处插入另一组字符。
 2、语法:STUFF ( character_expression , start , length ,character_expression )
 3、示例:在第一个字符串abcdef中删除从第2个位置(字符b)开始的3个字符,然后在删除的起始位置插入第二个字符串ijklmn,从而创建并返回一个新字符串
 SELECT STUFF(‘abcdef’, 2, 3, ‘ijklmn’)
 GO

下面是结果集:aijklmnef
4、参数:character_expression,一个字符数据表达式。character_expression可以是常量、变量,也可以是字符列或二进制数据列。
start,一个整数值,指定删除和插入的开始位置。如果start或length为负,则返回空字符串。如果start比第一个character_expression长,则返回空字符串。start可以是bigint类型。
length,一个整数,指定要删除的字符数。如果length比第一个character_expression长,则最多删除到最后一个 character_expression中的最后一个字符。length可以是bigint类型。
5、返回类型:如果 character_expression 是受支持的字符数据类型,则返回字符数据。如果 character_expression 是一个受支持的 binary 数据类型,则返回二进制数据。
6、备注:如果结果值大于返回类型支持的最大值,则产生错误。)
三、使用游标来合并数据,这个方法无版本限制

declare @t table(id int,value varchar(100))–定义结果集表变量
 –定义游标并进行合并处理
 declare my_cursor cursor local for
 select id, value from tb
 declare @id_old int, @id int, @value varchar(10), @s varchar(100)
 open my_cursor
 fetch my_cursor into @id, @value
 select @id_old = @id, @s=”
 while @@FETCH_STATUS = 0
 begin
     if @id = @id_old
        select @s = @s + ‘,’ + cast(@value as varchar)
     else
       begin
         insert @t values(@id_old , stuff(@s,1,1,”))
         select @s = ‘,’ + cast(@value as varchar) , @id_old = @id
       end
     fetch my_cursor into @id , @value
 END
 insert @t values(@id_old , stuff(@s,1,1,”))
 close my_cursor
 deallocate my_cursor select * from @t
 –drop table tb

这个一般常见用于数据统计,平常用的不多。