同事的一道面试题:
如何将某表中的某字段以逗号分隔拼接起来
在给出答案前,先给出测试用数据,与之前的几篇一样:
--DROPTABLE T
CREATETABLE T(GRP_A VARCHAR(20),GRP_B VARCHAR(20),GRP_C VARCHAR(20),VAL INT)
INSERT INTO T(GRP_A,GRP_B,GRP_C,VAL)
SELECT'a1','b1','c1',10 unionall
SELECT'a1','b1','c2',10 unionall
SELECT'a1','b2','c2',40 unionall
SELECT'a1','b2','c3',40 unionall
SELECT'a1','b2','c3',50 unionall
SELECT'a2','b3','c3',12 unionall
SELECT'a2','b3','c3',22 unionall
SELECT'a2','b3','c3',32
SELECT * FROM T
GRP_A GRP_B GRP_C VAL
-----------------------------------------------------------------------
a1 b1 c1 10
a1 b1 c2 10
a1 b2 c2 40
a1 b2 c3 40
a1 b2 c3 50
a2 b3 c3 12
a2 b3 c3 22
a2 b3 c3 32
(8 行受影响)
答案1:使用COALESCE函数
DECLARE @T NVARCHAR(200)
--SET @T = ''
SELECT @T = COALESCE(@T,'') + GRP_A + ','FROM T
SELECT @T
输出结果
------------------------
a1,a1,a1,a1,a1,a2,a2,a2,
DECLARE @T NVARCHAR(200)
--SET @T = ''
SELECT @T = ISNULL(@T,'') + GRP_A + ','FROM T
SELECT @T
输出结果与上边的一致哈不贴了
实际上,您应该已经发现了,这两个函数其实是个障眼术,即只要我的@T变量有初始化,完全可以直接拼接,算是答案3吧,代码如下:
DECLARE @T NVARCHAR(200)
SET @T = ''
SELECT @T = @T + GRP_A + ','FROM T
SELECT @T
输出结果与上上例一致不贴了
因为我们知道,在SQL中NULL表示UNKNOW类型,与任务字符串累加都会得到NULL值,如
DECLARE @T NVARCHAR(200)
SET @T = NULL
SET @T = @T + 'helloworld'
--PRINT @T这里不用PRINT是因为看不到实际输出什么
SELECT @T
实际输出
----
NULL
那么,再回来看上述所谓的答案1,2,3都不够严谨!即,如果该表中有那么一行,它的字段为NULL,会怎么办?!
答案3最终会返回NULL,答案1和答案2则跳过NULL值所在行以前所有的数据,返回NULL行以下的累加!所以嘞?要对GRP_A列进行是否是NULL值的验证!
实事上,COALESCE函数与ISNULL函数原本就是这个功能:返回表达式中第一个不为NULL的值,所谓障眼术即指此
下边的SQL脚本演示了两个函数的基本功能:
SET NOCOUNT ON
DECLARE @T CHAR(6)
SELECT'COALESCE',COALESCE(@T,NULL,NULL,'1234567890')
SELECT'ISNULL',ISNULL(@T,'1234567890')
输出结果
------------------
COALESCE 1234567890
------------
ISNULL 123456
先声明了一个类型为CHAR(6)的变量@T,没有设置值,默认为NULL
然后分别调用了ISNULL函数和COALESCE函数,ISNULL返回了符合变量定义类型的值,即截断后为CHAR(6)类型,而COALESCE则返回完完整整的字符串
简单对比下两个函数
两个函数都返回第一个不为空的表达式,
其一,ISNULL考虑变量类型,而COALESCE则不考虑
其二,ISNULL只接收两个参数,而COALESCE则可以接收多个参数
再回来看那个面试题,
如何将某表中的某字段以逗号分隔拼接起来?
答案4:
DECLARE @T NVARCHAR(200)
SET @T = ''
SELECT @T = @T + ISNULL(GRP_A,'NULL') + ','FROM T
SELECT @T
面试题这一部分结束,来看看ISNULL函数的应用实例
1,利用ISNULL函数干掉OR运算!
题目是查询表中VAL小于20的值,包括NULL值:
SELECT * FROM T WHERE ISNULL(VAL,-1) < 20
SELECT * FROM T WHERE VAL ISNULLOR VAL < 20
两个SQL具有相同的输出结果
GRP_A GRP_B GRP_C VAL
-----------------------------------------------------------------------
a1 b1 c1 10
a1 b1 c2 10
a2 b3 c3 NULL
a2 b3 c3 NULL
a2 b3 c3 NULL
(5 行受影响)
GRP_A GRP_B GRP_C VAL
-----------------------------------------------------------------------
a1 b1 c1 10
a1 b1 c2 10
a2 b3 c3 NULL
a2 b3 c3 NULL
a2 b3 c3 NULL
(5 行受影响)
2,ISNULL非主流更新表存储过程示例
如某更新表存储过程如下:
CREATEPROC UpdateT(
@ID INT,
@GRP_A VARCHAR(10) = NULL,
@GRP_B VARCHAR(10) = NULL,
@GRP_C VARCHAR(10) = NULL,
@VAL INT = 0
)AS
BEGIN
UPDATE T SET
GRP_A = @GRP_A,
GRP_B = @GRP_B,
GRP_C = @GRP_C,
VAL = @VAL
WHERE ID = @ID
END
当我们使用这个存储过程的时候,必须先得该行的所有记录,再把所有记录更新回去,可是这并不总是必须的
有时候手头只有两个数据:ID和VAL,我只想更新这个VAL
又有时候手头有另外两个数据:ID和GRP_A,这时候只更新GRP_A列即可
还有很多情况,如仅更新GRP_A,
仅更新GRP_A,GRP_B
仅更新GRP_A,GRP_B,GRP_C
仅更新GRP_A,GRP_B,GRP_C,VAL
...
这样的组合太多了,要想一劳永逸解决问题那就得更新任何字段前,先得到整行记录,再整行更新回去,于是多了一项工作:先查询,再更新
不爽不爽,那没有办法不先查询直接更新某一列呢?而且列可以任意组合?
在给出答案前,先声明一句:这个方法算不上完美解决方案,仅仅是个思路罢了,虽然我一直认为没什么影响,但如果要在正式项目中使用,建议还是多听听DBA的意见!
非主流更新任意列存储过程:
CREATEPROC UpdateT(
@ID INT,
@GRP_A VARCHAR(10) = NULL,
@GRP_B VARCHAR(10) = NULL,
@GRP_C VARCHAR(10) = NULL,
@VAL INT = 0
)AS
BEGIN
UPDATE T SET
GRP_A = ISNULL(@GRP_A,GRP_A),
GRP_B = ISNULL(@GRP_B,GRP_B),
GRP_C = ISNULL(@GRP_C,GRP_C),
VAL = ISNULL(@VAL,VAL)
WHERE ID = @ID
END
解读1上边的这个存储过程,假设参数@GRP_A为NULL时,经过ISNULL运算返回了GRP_A列!即实际变成了
SETGRP_A=ISNULL(NULL,GRP_A)
再演变为SETGRP_A=GRP_A!神马意思?什么也没更新…把自己更新为自己,等什么也没干,空忙活一场!但是
我们的效果达到了!@GRP_A参数为NULL时(不传递该参数,在定义存储过程时已经设计为可选参数),自己更新自己
当该参数不为NULL时,进行了实际的更新,其余三列以此类推,除@ID参数必须要传外,其它参数都是可选的!谁有值就更新谁,
什么模式?门面模式(又称外观模式),把小碎操作变成一个大的操作
解读2为什么第二部分都使用了ISNULL而不是COALESCE函数?
原因正是ISNULL会考虑第一个参数的类型声明从而自动截断超长部分数据!
如果用COALESCE的话可能会导致返回结果超出列定义!
产生将截断二进制字符串错误
http://www.cnblogs.com/kkun/archive/2011/08/17/2143220.html