在查询语句编写过程中,常常会遇到将ID相同的几个字段合并到一个字段的情况,今天就给大家分享在SQL Server中的字段进行合并的操作。

我们以合并打卡记录的例子来说明怎么合并。


我们来看下面两个截图,图一是原始的打卡记录表,EmpID表示员工的ID,该表记录了每个员工每次打卡的时间。每打一次卡就生成一行记录。

直接展示或导出图一的记录,就会不直观,不好看,如果变成图二那样的格式展示和导出就好看了,每个员工每一天的打卡记录都一目了然。没有打卡的日期,打卡记录就用null表示。


                    

sql server 字段合并 sql server 合并结果_字段

                           

                               图一(原始的打开记录)    


           

sql server 字段合并 sql server 合并结果_字段_02

                                    图二(展示的打卡记录)


下面就举例说明怎样将图一的原始表数据加工成图二的结果展示。


1,首先创建测试用的表,并插入数据。代码如下:

---员工信息表
 CREATE TABLE Employee (
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
EmpID INT NOT NULL,
EmpName NVARCHAR(50)     
 )

 INSERT INTO dbo.Employee (EmpID ,EmpName)
 SELECT 10001,'张三' UNION ALL
 SELECT 10002,'李四'


 ---打卡记录表
 CREATE TABLE CARD_RECORD (
     id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
     EmpID INT NOT NULL,
     FDateTime DATETIME NOT NULL
 )
 GO

 INSERT INTO CARD_RECORD (EmpID,FDateTime)
 SELECT '10001','2017-09-01 08:21'
 UNION ALL
 SELECT '10002','2017-09-01 08:22'
 UNION ALL
 SELECT '10001','2017-09-01 12:00'
 UNION ALL
 SELECT '10002','2017-09-01 12:01'
 UNION ALL
 SELECT '10001','2017-09-01 13:00'
 UNION ALL
 SELECT '10002','2017-09-01 13:01'
 UNION ALL
 SELECT '10002','2017-09-01 18:05'
 UNION ALL
 SELECT '10001','2017-09-01 18:12'
 UNION ALL
 SELECT '10002','2017-09-02 08:31'
 UNION ALL
 SELECT '10001','2017-09-02 08:42'
 UNION ALL
 SELECT '10001','2017-09-02 12:10'
 UNION ALL
 SELECT '10002','2017-09-02 12:11'
 UNION ALL
 SELECT '10001','2017-09-02 13:00'
 UNION ALL
 SELECT '10002','2017-09-02 13:11'
 UNION ALL
 SELECT '10001','2017-09-02 18:05'
 UNION ALL
 SELECT '10002','2017-09-02 18:12'
 UNION ALL
 SELECT '10002','2017-09-02 19:34'
 UNION ALL
 SELECT '10001','2017-09-03 08:36'
 UNION ALL
 SELECT '10002','2017-09-03 08:40'
 UNION ALL
 SELECT '10001','2017-09-03 12:20'
 UNION ALL
 SELECT '10002','2017-09-03 12:20'
 UNION ALL
 SELECT '10001','2017-09-03 12:55'
 UNION ALL
 SELECT '10002','2017-09-03 12:56'
 UNION ALL
 SELECT '10001','2017-09-03 18:05'
 UNION ALL
 SELECT '10001','2017-09-05 08:05'
 UNION ALL
 SELECT '10001','2017-09-05 12:30'
 UNION ALL
 SELECT '10001','2017-09-05 13:02'
 UNION ALL
 SELECT '10001','2017-09-05 18:10'
 GO

2,合并每个员工的打卡时间到一个字段内

合并打卡时间,我们使用 STUFF() 函数结合 For xml path 参数来对员工的打卡时间进行合并。语句如下:

----加工打卡记录表,将打卡时间合并到一个字段里面
SELECT a.EmpID,CAST(a.FDateTime AS DATE) dates,
times= STUFF( ( SELECT ','+ CONVERT(VARCHAR(5),FDateTime,108) FROM dbo.CARD_RECORD 
WHERE CAST(FDateTime AS DATE)= CAST(a.FDateTime AS DATE) AND EmpID=a.empid FOR XML PATH('') ) ,1,1,'')
FROM dbo.CARD_RECORD a 
--- WHERE  真实使用时,这里需要加where条件,查询一段时间内的打卡记录,否则查询出来的就是全部的打卡记录
GROUP BY a.EmpID,CAST(a.FDateTime AS DATE)


得到的结果如下图所示:

sql server 字段合并 sql server 合并结果_字段_03

说明:原始的打卡记录,是datetime型的,包含“年月日”和“时间”两部分,但在展示的时候“年月日”和“时间”是分开的,只需要将“时间”合并,“年月日”不需要合并,所以合并的是datetime型的“时间”部分。通过转换函数:CONVERT(VARCHAR(5),FDateTime,108)  即得到了FDateTime的“时间”的那部分。而CAST(a.FDateTime AS DATE) 是将FDateTime转换为Date型,Date型只有“年月日”,没有后面的“时间”。


简单来说明一下 STUFF()  函数结合 For xml path 参数是怎么实现字段合并的:

假如有这样一张表,表名是 Table_A

sql server 字段合并 sql server 合并结果_字段_04


 我想把它变成下面的格式:

sql server 字段合并 sql server 合并结果_sql server 字段合并_05


实现代码如下:

select 	类别, 
	名称 = ( stuff(
			(select ',' + 名称 from Table_A where 类别 = A.类别 for xml path('')),
			1,
			1,
			''
			)
	        ) 
from Table_A as A group by 类别
for xml path('') 
这句是把得到的内容以XML的形式显示。
stuff((select ',' + ep_name from ep_detail where ep_classes = a.ep_classes for xml path('')), 1, 1, '')
这句是把拼接的内容的第一个“,”去掉



3,构建日期

在完成第二步的时候,我们发现,员工打卡的日期是不完整的,没有打卡记录的那一天是没有记录的,我们需要将没有记录的那一天也展示出来,就需要构建当天的日期。

构建日期我们使用 Master 数据库中的系统表 spt_values 来生成连续的日期。代码如下:

declare @StartDate DATE = '2017-09-01'
 declare @EndDate DATE ='2017-09-05'--利用master库spt_values表构建时间,关联员工信息表,将每个员工附上构建的时间
SELECT b.EmpID,b.EmpName,dateadd(day,a.number,@StartDate) as dates
from master.dbo.spt_values a CROSS APPLY dbo.Employee b  
WHERE a.type ='P' and a.number <=DATEDIFF(day, @StartDate, @EndDate)


查询结果如下所示:


sql server 字段合并 sql server 合并结果_sql server_06


这样每个员工就给他构建了查询范围内连续的日期。将第二步和第三步查询的结果整合,就可以得到最终的结果。


整合后完整的代码如下:

/***查询数据***/
 declare @StartDate DATE = '2017-09-01'       ----查询的开始日期
 declare @EndDate DATE ='2017-09-05'         ----查询的结束日期


 SELECT aa.EmpID,aa.EmpName,aa.dates,bb.times
 FROM (
--利用master库spt_values表构建时间,关联员工信息表,将每个员工附上构建的时间
SELECT b.EmpID,b.EmpName,dateadd(day,a.number,@StartDate) as dates
from master.dbo.spt_values a CROSS APPLY dbo.Employee b  
WHERE a.type ='P' and a.number <=DATEDIFF(day, @StartDate, @EndDate)
 ) aa LEFT JOIN (
--加工打卡记录表,将打卡时间合并到一个字段里面
SELECT a.EmpID,CAST(a.FDateTime AS DATE) dates,
times= STUFF( ( SELECT ','+ CONVERT(VARCHAR(5),FDateTime,108) FROM dbo.CARD_RECORD WHERE CAST(FDateTime AS DATE)= CAST(a.FDateTime AS DATE) AND EmpID=a.empid FOR XML PATH('') ) ,1,1,'')
FROM dbo.CARD_RECORD a 
WHERE a.FDateTime >= @StartDate AND a.FDateTime <= @EndDate
GROUP BY a.EmpID,CAST(a.FDateTime AS DATE)
 ) bb ON aa.EmpID=bb.EmpID AND aa.dates=bb.dates


这样,我们就能得到 9月1日至 9月5日的员工打卡记录,当天如果没有打卡记录,则显示为null,而且日期也是连续的显示出来。


希望以上方法能够帮助到大家。