SQLServer字符串聚合解决方法(CLR)

开发环境:SQL Server2008 R2

写个综合视图,遇到个情况,需要对字符串进行聚合统计,简化如下:

任务号 提交人 完工数 周转车号
X01 张三 300 V001
X01 李四 200 V002
X02 王五 600 V003
X02 马六 400 V004
X02 赵七 100 V005

目的是:需要列出统计任务的完成信息如下:

任务号 提交人 完工数 周转车号
X01 张三,李四 500 V001,V002
X02 王五,马六,赵七 1100 V003,V004,V005

完工数量可以直接sum 后 group by,但是提交人 和 周转车 字符串字段就很麻烦了。google了下,有以下三种办法:

  • ** 自定义聚合函数 ** 如何在sql server的group by语句中聚合字符串字段
    这种方法的思路就是用sql自定义个function,聚合的时候调用。这个办法最大的问题就是在函数中需要把要调用的表名写死,像上面这个需求,就要定义两个函数,一个是对提交人的聚合,一个是对周转车的聚合,而且这里的识别id只有一个,就是任务id(这个是简化需求),我的实际需求是要对任务ID+工序ID作为子件的,这样的函数条件也不好扩展。--所以放弃这个办法。
  • ** 用stuff和for xml path子查询 ** SQL SERVER 2005 中使用for xml path('')和stuff合并显示多行数据到一行中
    这个方法也可行,但是问题也和1一样,要大段大段的写SQL子查询,而且无法复用,多的话实在受不了。
  • ** 目前找到的以为最好的方法:配合c#自定义聚合函数 ** 源出处:C#实现SQL Server2005的扩展聚合函数
    该方法实现后,调用的SQL就是:
SELECT taskid,SUM(qty),
dbo.StrJoin(workerName,',') as workers, dbo.StrJoin(cartNo,',') as Carts 
FROM taskExecs  GROUP BY taskid

是不是很简单?而且以后出现类似的拼接字符串聚合就都直接调用就好了,一副一劳永逸的姿态。
我对原文的方法做了一些小调整和改变,具体实现如下:

  1. Visual Studio 2015,新建个项目--》模板选SQL Server 数据库项目,命名项目sqlUtil
  2. 新建项--》 SQL CLR c# ==>SQL CLR c# 聚合 ,是个类,命名StrJoin.cs
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text;、
[Serializable]
[SqlUserDefinedAggregate(
  Format.UserDefined, //use custom serialization to serialize the intermediate result
  IsInvariantToNulls = true, //optimizer property
  IsInvariantToDuplicates = false, //optimizer property
  IsInvariantToOrder = false, //optimizer property
  MaxByteSize = 8000), //maximum size in bytes of persisted value
]

public struct StrJoin : IBinarySerialize
{
  private StringBuilder sbIntermediate;
  public void Init()
  {
    sbIntermediate = new StringBuilder();
  }
  public void Accumulate(SqlString Value,SqlString contChar)
  {
    if (Value == null || Value.ToString().ToLower().Equals("null"))
    {
      return;
    }
    else
    {
      sbIntermediate.Append(Value).Append(contChar);
    }
  }
  public void Merge(StrJoin Group)
  {
    sbIntermediate.Append(Group.sbIntermediate);
  }
  public SqlString Terminate()
  {
    string output = String.Empty;
    if (sbIntermediate != null && sbIntermediate.Length>0)
    {
      output = sbIntermediate.ToString(0, sbIntermediate.Length - 1);
    }
    return new SqlString(output);
  }
  // This is a place-holder member field
  #region IBinarySerialize Members
  public void Read(System.IO.BinaryReader r)
  {
    sbIntermediate = new StringBuilder(r.ReadString());
  }
  public void Write(System.IO.BinaryWriter w)
  {
    w.Write(this.sbIntermediate.ToString());
  }
  #endregion
}

说明:看上去一脸蒙逼很复杂的样子,其实以上函数有效的部分很简单,重点部分就是

  1. 在Accumulate函数中:传入参数,把字符串拼起来。
  2. 在Terminate函数中: 去掉最后一个连接符并输出。
    主要看这两个动作,就知道了。
    在sqlserver中执行如下:
--打开SQLSERVER的CLR功能
EXEC sp_configure 'clr enabled', 1
RECONFIGURE WITH OVERRIDE
GO

--注册DLL
CREATE ASSEMBLY sqlUtil FROM 'C:\sqlUtil.dll'      --生成的DLL路径
GO

--注册函数
CREATE AGGREGATE [dbo].[StrJoin] (@Value [nvarchar](MAX), 
  @contChar [nvarchar](2))
  RETURNS [nvarchar](MAX)
  EXTERNAL NAME [sqlUtil].[StrJoin]

这样后,就可以愉快的使用了。

如果要更新dll,需要先drop,在create

顺序是 删除引用的函数-->删除dll

DROP AGGREGATE StrJoin
DROP  ASSEMBLY sqlUtil

PS:在这个过程遇到个纠结的问题,就是虚拟机和远程机之间复制文件的时候,居然会有问题,导致一个更新的dll一直是旧版本,而我却以为代码有错。。。。最后用.Net Refector去看dll的函数,才惊觉这个问题,吐血中.... 最后还是用共享传的文件。
*** PS2:据说MYSQL和Oracle其实都有现成的group_contact 和 wm_concat,所以到了SQLSERVER2012,据说也支持了字符串聚合的函数。但是在使用2012之前,等于是用第三种方法提前体验了而已。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容