最近做一个项目,遇到一个在分组的情况下,将某一列的字段值(varchar
类型)连接起来的问题,类似于sum
函数对int
型字段值求和。 如有一个表t_table
,结构和数据如图1
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 图1
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 其中要按着xh字段分组,并且将每一组name字段值连接起来。最终结果希望如图2所示nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图2nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u表中的th
字段值对于每一个xh
值是唯一的,也是有限的,也就是说,对于一个xh
值,th
的值不会太多,如最多是10
个(从1
至10
)。nÞwww.netcsharp.cn[Ç0¯~u以上需求最终想了三种方法来解决这个问题。nÞwww.netcsharp.cn[Ç0¯~u一、修改表结构nÞwww.netcsharp.cn[Ç0¯~u如果是新的项目,可以考虑修改一下表的结构。如果t_table
的结构修改如下:nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u xh value1 value2
nÞwww.netcsharp.cn[Ç0¯~uvalue3 value4 .... .... value10
nÞwww.netcsharp.cn[Ç0¯~u 0001 123456 654321 456789
nÞwww.netcsharp.cn[Ç0¯~u 0002 12abcd 4d2r343 343dfd
nÞwww.netcsharp.cn[Ç0¯~u 0003 abcde3 132323
nÞwww.netcsharp.cn[Ç0¯~u这种方法将value
的值纵向改为横向,也就是说,按每一个xh
值,将value
字段的值按逆时针旋转了90
度。 但这种方法要有一个前提,就是假设xh
的每一个值所对应的value
值不会太多,如上面不超过10
个,这样才有可能建立有限个字段。如果按着上面的字段结构,只需要将这些字段加一起就可以了,也不用分组。如下所示:nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~uselect xh , (value1 + value2 + value3 +
+ value10) as value from t_tablenÞwww.netcsharp.cn[Ç0¯~u但这种方法至少有如下三个缺陷:nÞwww.netcsharp.cn[Ç0¯~u1.
需要修改表结构,这对于已经进行很长时间或是已经上线的项目产不适用nÞwww.netcsharp.cn[Ç0¯~u2.
对每一个xh
字段的value
取值数有限制,如果太多,就得建立很多字段。这样性能会降低。nÞwww.netcsharp.cn[Ç0¯~u3.
这样做虽然查询容易,但如果需要对每一个xh
的不同值频繁修改或加入新的值时,如果把它们都放到一行,容易因为行锁而降低性能。nÞwww.netcsharp.cn[Ç0¯~u二、动态生成select语句nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u让我们先看三条SQL语句:nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
select xh,value as th1 from t_table where th=1 nÞwww.netcsharp.cn[Ç0¯~u
select xh,value as th2 from t_table where th=2nÞwww.netcsharp.cn[Ç0¯~u
select xh,value as th3 from t_table where th=3nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u这三条语句分别使用th字段按着所有th可能的值来查询t_table,这三条SQL语句所查询出来的记录如图3所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图 3nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
然后再使用下面的语句按着xh
分组:nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~uselect xh from t_table group by xhnÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u得到的结果如图4所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图4nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
然后使用left join
,以图4
所示的表为最左边的表,进行连接,SQL
语句如下:nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~uselect a.xh, b.th1, c.th2, d.th3 fromnÞwww.netcsharp.cn[Ç0¯~u
(select xh from t_table group by xh) a nÞwww.netcsharp.cn[Ç0¯~u
left join nÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th1 from t_table where th=1) b on a.xh=b.xh nÞwww.netcsharp.cn[Ç0¯~u
left join nÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th2 from t_table where th=2) c on a.xh=c.xh nÞwww.netcsharp.cn[Ç0¯~u
left join nÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th3 from t_table where th=3) d on a.xh=d.xhnÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 之所以使用left join,是因为按着th查询后,有的表的某些xh值可以没有,如图3中的第三个表,就没有0003。如果使用内连接,0003就无法在记录集中体现。这面的SQL的查询结果如图5所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图5nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
然后我们就可以使用如下的语句来连接th1
、th2
和th3
了。 nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~uselect xh, (th1+th2+th3) as th from myviewnÞwww.netcsharp.cn[Ç0¯~umyview
表示将上面用left join
的语句保存成的视图。nÞwww.netcsharp.cn[Ç0¯~u下面可以将这个过程写成一条SQL语句:nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
select xh, (th1+th2+th3) as th from nÞwww.netcsharp.cn[Ç0¯~u
(nÞwww.netcsharp.cn[Ç0¯~u
select a.xh, (case when b.th1 is null then '' else b.th1 end) as th1, (casenÞwww.netcsharp.cn[Ç0¯~uwhen c.th2 is null then '' else c.th2 end) as th2, (casenÞwww.netcsharp.cn[Ç0¯~uwhen d.th3 is null then '' else d.th3 end) as th3 fromnÞwww.netcsharp.cn[Ç0¯~u
(select xh from t_table group by xh) a nÞwww.netcsharp.cn[Ç0¯~u
left join nÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th1 from t_table where th=1) b on a.xh=b.xh nÞwww.netcsharp.cn[Ç0¯~u
left join nÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th2 from t_table where th=2) c on a.xh=c.xh nÞwww.netcsharp.cn[Ç0¯~u
left joinnÞwww.netcsharp.cn[Ç0¯~u
(select xh,value as th3 from t_table where th=3) d on a.xh=d.xhnÞwww.netcsharp.cn[Ç0¯~u
) xnÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 由于null
加上任何字符串都为null
,因此,使用case
语句来将null
转换为空串。上面的SQL
就会得到图2
所示的查询结果。也许有的读者会问,如果th
的可能取值可变呢!如xh
为0001
的th
值四个:1
至4
。 那上面的SQL
不是要再加一个left join
吗?这样不是很不通用。 要解决这个问题也很容易。可以使用程序(如C#
、Java
等)自动生成上述的SQL
,然后由程序提交给数据库,再执行。 当然,这需要程序事先知道th
值对于当前程序最多有几个值,然后才可以自动生成上述的SQL
语句。nÞwww.netcsharp.cn[Ç0¯~u这种方法几乎适合于所有的数据库,不过如果th的取值比较多的话,可能SQL语句会很长,但是如果用程序自动生成的话,就不会管这些了。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
三、使用C#实现SQL Server2005的扩展聚合函数(当然,也可以用VB.NET)nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u这一种方法笔者认为是最“酷”的方法。因为每一个人都只想写如下的SQL语句就可以达到目录。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
select xh, dbo.joinstr(value) from t_table group by xhnÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u其中joinstr
是一个聚合函数,功能是将每一组的某个字符串列的值首尾连接。上面的SQL
也可以查询图2
所示的结果。但遗憾的是,sql server2005
并未提供可以连接字符串的聚合函数。下面我们就来使用C#
来实现一个扩展聚合函数。nÞwww.netcsharp.cn[Ç0¯~u 首先用VS2008/VS2005建立一个SQL Server项目,如图6所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 图6
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u点击“确定”按钮后,SQL Server项目会要求连接一个数据库,我们可以选择一个数据库,如图7所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图7nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u然后在工程中加入一个聚合类(joinstr.cs),如图8所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图8nÞwww.netcsharp.cn[Ç0¯~u
joinstr.cs中的最终代码如下:nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
using System;nÞwww.netcsharp.cn[Ç0¯~u
using System.Data;nÞwww.netcsharp.cn[Ç0¯~u
using Microsoft.SqlServer.Server;nÞwww.netcsharp.cn[Ç0¯~u
using System.Data.SqlTypes;nÞwww.netcsharp.cn[Ç0¯~u
using System.IO;nÞwww.netcsharp.cn[Ç0¯~u
using System.Text;nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
[Serializable]nÞwww.netcsharp.cn[Ç0¯~u
[SqlUserDefinedAggregate(nÞwww.netcsharp.cn[Ç0¯~u
Format.UserDefined, //use custom serialization to serialize the intermediate resultnÞwww.netcsharp.cn[Ç0¯~u
IsInvariantToNulls = true, //optimizer propertynÞwww.netcsharp.cn[Ç0¯~u
IsInvariantToDuplicates = false, //optimizer propertynÞwww.netcsharp.cn[Ç0¯~u
IsInvariantToOrder = false, //optimizer property nÞwww.netcsharp.cn[Ç0¯~u
MaxByteSize = 8000) //maximum size in bytes of persisted valuenÞwww.netcsharp.cn[Ç0¯~u
]nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public struct joinstr :IBinarySerializenÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
private System.Text.StringBuilder intermediateResult;nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public void Init()nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
// 在此处放置代码nÞwww.netcsharp.cn[Ç0¯~u
intermediateResult = new System.Text.StringBuilder();nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public void Accumulate(SqlString Value)nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
intermediateResult.Append(Value.Value);nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public void Merge(joinstr Group)nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
intermediateResult.Append(Group.intermediateResult);nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public SqlString Terminate()nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
return new SqlString(intermediateResult.ToString());nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public void Read(BinaryReader r)nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
intermediateResult = new StringBuilder(r.ReadString());nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
public void Write(BinaryWriter w)nÞwww.netcsharp.cn[Ç0¯~u
{nÞwww.netcsharp.cn[Ç0¯~u
w.Write(this.intermediateResult.ToString());nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
}nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u
由于本例需要聚合字符串,而不是已经被序列化的类型,如int
等,因此,需要实现IBinarySerialize
接口来手动序列化。使用C#
实现SQL Server
聚合函数,也会受到字符串最大长度为8000
的限制。nÞwww.netcsharp.cn[Ç0¯~u 在编写完上述代码后,可以使用Visual Studio来部署(右向工程,在弹出菜单上选“部署”即可)。也可以使用SQL语句来部署。假设上面的程序生成的dll为MyAggregate.dll,可以使用下面的SQL语句来部署:nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
CREATE ASSEMBLY MyAgg FROMnÞwww.netcsharp.cn[Ç0¯~u'D:\test\MyAggregate.dll'nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
CREATE AGGREGATE joinstr (@inputnÞwww.netcsharp.cn[Ç0¯~unvarchar(200)) RETURNSnÞwww.netcsharp.cn[Ç0¯~unvarchar(max)nÞwww.netcsharp.cn[Ç0¯~u
EXTERNAL NAME MyAgg.joinstrnÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 要注意的是,字符串类型需要用nvarchar,而不能用varchar。
nÞwww.netcsharp.cn[Ç0¯~u第一条SQL
语句是装载dll
,第二条SQL
语句是注册joinstr
聚合函数(每一个C#
类就是一个聚合函数)nÞwww.netcsharp.cn[Ç0¯~u在执行上面的SQL语句之前,需要将SQL Server2005的clr功能打开。如图9所示。nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
图9nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
如果想删除上面建立的聚合函数,可以使用如下的SQL语句:nÞwww.netcsharp.cn[Ç0¯~u
nÞwww.netcsharp.cn[Ç0¯~u
drop aggregate joinstrnÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u
在删除聚合函数后,可以将MyAggregate.dll
卸载。nÞwww.netcsharp.cn[Ç0¯~udrop assembly MyAggnÞwww.netcsharp.cn[Ç0¯~uOK
,现在可以使用joinstr
来聚合字符串了。nÞwww.netcsharp.cn[Ç0¯~unÞwww.netcsharp.cn[Ç0¯~u 这种方法虽然显示很“酷”,但却要求开发人员熟悉扩展聚合函数的开发方法,如果开发人员使有的不是微软的开发工具,如使用Java
,恐怕这种方法就只能是空谈了(除非开发小组内有人会用微软的开发工具)。nÞwww.netcsharp.cn[Ç0¯~u当然,如果使用其他的数据库,如oracle
、mysql
,也是可以实现类似扩展函数的功能的,如oracle
可以使用java
来进行扩展。但这要求开发人员具有更高的素质。nÞwww.netcsharp.cn[Ç0¯~u