表A,两个字段: CreateDate, Detail
---------------------------------------
CreateDate Detail
2009-11-26 00:00:00.000 (A3-5)
2009-11-26 00:00:00.000 (B3-2)
2009-11-27 00:00:00.000 (A3-3)
2009-11-27 00:00:00.000 (B3-1)
---------------变成---------------------
2009-11-26 00:00:00.000 (A3-5)#(B3-2)
2009-11-27 00:00:00.000 (A3-3)#(B3-1)
-------------------------------------
-- Author : liangCK 梁爱兰
-- Comment: 小梁 爱 兰儿
-- Date : 2009-11-27 11:10:52
-------------------------------------
--> 生成测试数据: @tb
DECLARE @tb TABLE (CreateDate datetime,Detail varchar(6))
INSERT INTO @tb
SELECT '2009-11-26 00:00:00.000','(A3-5)' UNION ALL
SELECT '2009-11-26 00:00:00.000','(B3-2)' UNION ALL
SELECT '2009-11-27 00:00:00.000','(A3-3)' UNION ALL
SELECT '2009-11-27 00:00:00.000','(B3-1)'
--SQL查询如下:
SELECT A.CreateDate,STUFF(B.x,1,1,'') AS Details
FROM (SELECT DISTINCT CreateDate FROM @tb) AS A
CROSS APPLY (
SELECT x=(SELECT '#'+Detail AS [text()]
FROM @tb
WHERE CreateDate=A.CreateDate
FOR XML PATH(''))
) AS B
=============
STUFF ( character_expression , start , length ,character_expression )
参数
character_expression
一个字符数据表达式。character_expression 可以是常量、变量,也可以是字符列或二进制数据列。
start
一个整数值,指定删除和插入的开始位置。如果 start 或 length 为负,则返回空字符串。如果 start 比第一个 character_expression 长,则返回空字符串。start 可以是 bigint 类型。
length
一个整数,指定要删除的字符数。如果 length 比第一个 character_expression 长,则最多删除到最后一个 character_expression 中的最后一个字符。length 可以是 bigint 类型。
返回类型
如果 character_expression 是受支持的字符数据类型,则返回字符数据。如果 character_expression 是一个受支持的 binary 数据类型,则返回二进制数据。
===========================
Cross Apply使表可以和表值函数结果进行join,在下面的示例中建了两个表和一个表值函数,T_b的列a_ids中会存放a表的id用,分割的字符串连接;我们通过cross apply使T_a,T_b表通过splitIDs inner join 连接。请看示例:GO
if object_id('T_a','U') is not null
drop table T_a
GO
CREATE TABLE T_a( id int unique not null,
name varchar(50),
)
GO
if object_id('T_b',N'U') is not null
drop table T_b
GO
create table T_b
(
id int unique not null,
name varchar(10),
a_ids varchar(100) null --要在这一列中存放t_a表的ID序列,这样做连第一范式都没有满足,但是有时候考虑性能或设计我们可能会像这么用
)
GO
--初始化数据
INSERT INTO T_a VALUES(1,'A-1')
INSERT INTO T_a VALUES(2,'A-2')
INSERT INTO T_a VALUES(3,'A-3')
INSERT INTO T_a VALUES(4,'A-4')
INSERT INTO T_a VALUES(5,'A-5')
INSERT INTO T_b VALUES(1,'B-1','1,2,4')
GO
--创建一个表值函数,用来拆分用逗号分割的数字串,返回只有一列数字的表
if object_id('splitIDs','TF') is not null
drop function splitIDs;
GO
create function splitIDs(
@Ids nvarchar(1000)
)
returns @t_id TABLE (id bigint)
as
begin
declare @i int,@j int,@l int,@v bigint;
set @i = 0;
set @j = 0;
set @l = len(@Ids);
while(@j < @l)
begin
set @j = charindex(',',@Ids,@i+1);
if(@j = 0) set @j = @l+1;
set @v = cast(SUBSTRING(@Ids,@i+1,@j-@i-1) as bigint);
INSERT INTO @t_id VALUES(@v)
set @i = @j;
end
return;
end
GO
--测试splitIDs的执行效果
select * from splitIDs('1,2,4,3')
select * from splitIDs('100')
select * from splitIDs(NULL)
GO
--使用cross apply获得t_b表中指定行对应的所有t_a表中的记录
select
aid = t_a.id
,aname = t_a.name
,bid = t_b.id
from t_b
cross apply splitIDs(a_ids) tbl_Ids
INNER JOIN t_a ON tbl_Ids .id = t_a.id
where t_b.id = 1
备注
如果开始位置或长度值是负数,或者如果开始位置大于第一个字符串的长度,将返回空字符串。如果要删除的长度大于第一个字符串的长度,将删除到第一个字符串中的第一个字符。
如果结果值大于返回类型支持的最大值,则产生错误。
示例
以下示例在第一个字符串 abcdef 中删除从第 2 个位置(字符 b)开始的三个字符,然后在删除的起始位置插入第二个字符串,从而创建并返回一个字符串。
SELECT STUFF('abcdef', 2, 3, 'ijklmn');
GO
下面是结果集:
---------
aijklmnef
(1 row(s) affected)
=====================
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('lzy')
再次运行上述脚本,将生成如下的结果:
<lzy>
<UserID>1</UserID>
<UserName>a</UserName>
</lzy>
<lzy>
<UserID>2</UserID>
<UserName>b</UserName>
</lzy>
可以看到节点变成,其实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