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

開發(fā)環(huán)境:SQL Server2008 R2

寫個綜合視圖,遇到個情況,需要對字符串進(jìn)行聚合統(tǒng)計(jì),簡化如下:

任務(wù)號 提交人 完工數(shù) 周轉(zhuǎn)車號
X01 張三 300 V001
X01 李四 200 V002
X02 王五 600 V003
X02 馬六 400 V004
X02 趙七 100 V005

目的是:需要列出統(tǒng)計(jì)任務(wù)的完成信息如下:

任務(wù)號 提交人 完工數(shù) 周轉(zhuǎn)車號
X01 張三,李四 500 V001,V002
X02 王五,馬六,趙七 1100 V003,V004,V005

完工數(shù)量可以直接sum 后 group by,但是提交人 和 周轉(zhuǎn)車 字符串字段就很麻煩了。google了下,有以下三種辦法:

  • ** 自定義聚合函數(shù) ** 如何在sql server的group by語句中聚合字符串字段
    這種方法的思路就是用sql自定義個function,聚合的時候調(diào)用。這個辦法最大的問題就是在函數(shù)中需要把要調(diào)用的表名寫死,像上面這個需求,就要定義兩個函數(shù),一個是對提交人的聚合,一個是對周轉(zhuǎn)車的聚合,而且這里的識別id只有一個,就是任務(wù)id(這個是簡化需求),我的實(shí)際需求是要對任務(wù)ID+工序ID作為子件的,這樣的函數(shù)條件也不好擴(kuò)展。--所以放棄這個辦法。
  • ** 用stuff和for xml path子查詢 ** SQL SERVER 2005 中使用for xml path('')和stuff合并顯示多行數(shù)據(jù)到一行中
    這個方法也可行,但是問題也和1一樣,要大段大段的寫SQL子查詢,而且無法復(fù)用,多的話實(shí)在受不了。
  • ** 目前找到的以為最好的方法:配合c#自定義聚合函數(shù) ** 源出處:C#實(shí)現(xiàn)SQL Server2005的擴(kuò)展聚合函數(shù)
    該方法實(shí)現(xiàn)后,調(diào)用的SQL就是:
SELECT taskid,SUM(qty),
dbo.StrJoin(workerName,',') as workers, dbo.StrJoin(cartNo,',') as Carts 
FROM taskExecs  GROUP BY taskid

是不是很簡單?而且以后出現(xiàn)類似的拼接字符串聚合就都直接調(diào)用就好了,一副一勞永逸的姿態(tài)。
我對原文的方法做了一些小調(diào)整和改變,具體實(shí)現(xiàn)如下:

  1. Visual Studio 2015,新建個項(xiàng)目--》模板選SQL Server 數(shù)據(jù)庫項(xiàng)目,命名項(xiàng)目sqlUtil
  2. 新建項(xiàng)--》 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
}

說明:看上去一臉蒙逼很復(fù)雜的樣子,其實(shí)以上函數(shù)有效的部分很簡單,重點(diǎn)部分就是

  1. 在Accumulate函數(shù)中:傳入?yún)?shù),把字符串拼起來。
  2. 在Terminate函數(shù)中: 去掉最后一個連接符并輸出。
    主要看這兩個動作,就知道了。
    在sqlserver中執(zhí)行如下:
--打開SQLSERVER的CLR功能
EXEC sp_configure 'clr enabled', 1
RECONFIGURE WITH OVERRIDE
GO

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

--注冊函數(shù)
CREATE AGGREGATE [dbo].[StrJoin] (@Value [nvarchar](MAX), 
  @contChar [nvarchar](2))
  RETURNS [nvarchar](MAX)
  EXTERNAL NAME [sqlUtil].[StrJoin]

這樣后,就可以愉快的使用了。

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

順序是 刪除引用的函數(shù)-->刪除dll

DROP AGGREGATE StrJoin
DROP  ASSEMBLY sqlUtil

PS:在這個過程遇到個糾結(jié)的問題,就是虛擬機(jī)和遠(yuǎn)程機(jī)之間復(fù)制文件的時候,居然會有問題,導(dǎo)致一個更新的dll一直是舊版本,而我卻以為代碼有錯。。。。最后用.Net Refector去看dll的函數(shù),才驚覺這個問題,吐血中.... 最后還是用共享傳的文件。
*** PS2:據(jù)說MYSQL和Oracle其實(shí)都有現(xiàn)成的group_contact 和 wm_concat,所以到了SQLSERVER2012,據(jù)說也支持了字符串聚合的函數(shù)。但是在使用2012之前,等于是用第三種方法提前體驗(yàn)了而已。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容