在项目开发中,有时会碰到将列记录合并为一行的情况,例如根据地区将人员姓名合并,或根据拼音首字母合并城市等,下面就以根据地区将人员姓名合并为例,详细讲一下合并的方法。
首先,先建一个表,并添加一些数据,建表代码如下:
If OBJECT_ID(N'Demo') Is Not Null
Begin
Drop Table Demo
End
Else
Begin
Create Table Demo(
Area nvarchar(30),
Name nvarchar(20))Insert Into Demo(Area,Name)
Values(N'北京',N'张三'),
(N'上海',N'李四'),
(N'深圳',N'王五'),
(N'深圳',N'钱六'),
(N'北京',N'赵七'),
(N'北京','Tom'),
(N'上海','Amy'),
(N'北京','Joe'),
(N'深圳','Leo')
End
Go
建完后查询一下,可见表中数据如下:
如果仅将Name列合并,不遵循任何条件的话,我们可以采用两种方法,第一种就是采用FOR XML PATH方式,代码如下:
SELECT ','+Name FROM dbo.Demo FOR XML PATH('')
运行结果如下:
关于FOR XML PATH的详细介绍可参考MSDN:搭配 FOR XML 使用 PATH 模式
第二种方法就是定义一个变量用来装载查询的结果,代码如下:
Declare @NameCollection nvarchar(500)
Select @NameCollection=ISNULL(@NameCollection+',','')+Name From dbo.Demo
Select @NameCollection as NameCollection
运行结果如下:
加了ISNULL是因为最开始变量@NameCollection为NULL,为了避免“张三”前多一个逗号(“,”)而采用的替换。
上面讲了在无条件的情况下合并一列,但是在项目中几乎不会遇到这样的情况,一般都是根据某一列来合并另一列的数据,例如我们现在要根据Area将Name合并,得到这样的结果:
有了上面的基础,要合并成这样的数据就容易了,我们只需要针对Area列采用聚合GROUP BY或取不重复值DISTINCT,然后根据Area列合并Name列,有了思路,下面就来说说如何实现,首先还是采用FOR XML PATH方式,结合自连接,首先先按Area列对Name列进行合并,代码如下:
SELECT Area,
(SELECT ','+Name FROM dbo.Demo WHERE Area = t.Area FOR XML PATH(''))
AS NameCollection FROM dbo.Demo AS t
运行结果如下:
现在有两点还没实现,第一是结果重复了,第二是NameCollection列最开始都多了一个逗号,先去掉逗号,采用STUFF 函数来进行替换,代码修改如下:
SELECT Area,
STUFF((SELECT ','+Name FROM dbo.Demo WHERE Area = t.Area FOR XML PATH('')),1,1,'')
AS NameCollection FROM dbo.Demo AS t
现在运行后结果如下:
下面就剩下去掉重复数据了,分别采用GROUP BY和DISTINCT,代码如下:
SELECT DISTINCT Area,
STUFF((SELECT ','+Name FROM dbo.Demo WHERE Area = t.Area FOR XML PATH('')),1,1,'')
AS NameCollection FROM dbo.Demo AS t
SELECT Area,
STUFF((SELECT ','+Name FROM dbo.Demo WHERE Area = t.Area FOR XML PATH('')),1,1,'')
AS NameCollection FROM dbo.Demo AS t GROUP BY Area
关于STUFF函数可以参考MSDN介绍:STUFF函数
运行结果即为最终我们需要的结果,最开始在上面讲到了一种用变量来装载查询结果实现合并一列的方法,下面详细介绍如何采用上述方法来实现我们的需求,我们可以根据上面的方法建一个函数,传入一个Area参数,根据Area来进行合并,返回合并值,函数如下:
CREATE FUNCTION MergeByColumn
(
-- Add the parameters for the function here
@Area nvarchar(30)
)
RETURNS nvarchar(500)
AS
BEGIN
-- Declare the return variable here
DECLARE @NC nvarchar(500)-- Add the T-SQL statements to compute the return value here
SELECT @NC=ISNULL(@NC+',','')+Name FROM dbo.Demo WHERE Area=@Area-- Return the result of the function
RETURN @NCEND
GO
建好后测试下,以传入参数为“北京”为例,运行如下代码:
SELECT dbo.MergeByColumn('北京') AS NameCollection
得到结果如下:
现在只需将Area列也加入查询即可,修改代码如下:
SELECT Area,dbo.MergeByColumn(Area) AS NameCollection From dbo.Demo
现在也得到了重复的结果,如下:
去重复同样可以用GROUP BY和DISTINCT,代码如下,即可以得到我们最终的结果:
SELECT DISTINCT Area,dbo.MergeByColumn(Area) AS NameCollection From dbo.Demo
SELECT Area,dbo.MergeByColumn(Area) AS NameCollection From dbo.Demo GROUP BY Area