2009-05-25 164 views
4

我正在編寫一個導出函數,我需要將聯繫人導出到Excel,並且遇到了技術障礙 - 或者我的SQL技能中的差距更接近事實。 ;)TSQL逗號分隔

這裏是場景: 我有一堆數據庫中的聯繫人。每個聯繫人可以有許多不同的角色,例如,聯繫人既可以是C#開發人員,也可以是DBA,也可以是DBA和IT經理。這些分爲三個表,如下所示:

------------------- ------------------- ------------------- 
*  Contact  * * ContactRole * *  Role  * 
------------------- ------------------- ------------------- 
* ID    * * ContactID  * * ID    * 
* Name   * * RoleID   * * Name   * 
* Address   * ------------------- ------------------- 
------------------- 

不太難遵循。有一組聯繫人和一組角色。這些由相應ID上的ContactRole表連接。

當我導出聯繫人時,我需要在導出時使用所有角色逗號分隔的列,如C# Developer, DBADBA, IT-manager。 導出將從ASP.NET/C#codebehind完成,所以我想我可以在代碼中做到這一點,但我有一種感覺可以在SQL中做。

數據來自SQL Server 2005中

回答

4

試試這個

declare @Roles nvarchar(max) 

select @Roles = case when @Roles is null then '' else @Roles + ', ' end + Role.Name 
from Role 
inner join ContactRole on Role.ID = ContactRole.RoleID 
where ContactRole.ContactID = @ContactID 

select @Roles 

更新:

上面的代碼覆蓋功能,單個聯繫人。您可以從

Select Name, dbo.GetContactRoles(ID) From Contact 
+0

謝謝,這正是我所追求的! – 2009-05-25 09:06:18

+1

由於SQL Server 2005不能很好地處理子查詢,因此您可能會遇到性能較差的大型結果集,因此您應該使用此函數監視查詢。 – 2009-05-25 09:49:58

1

SQL查詢:

SELECT Contact.Name as cName, Role.Name as rName FROM Contact 
JOIN ContactRole ON (Contact.ID==ContactRole.ContactID) 
JOIN Role ON ON (Role.ID==ContactRole.RoleID) 

下一頁使用應用程序邏輯進行

forloop: 
    array[ cName ] .= rName.', '; 
endforloop; 
5

可以使用一個CLR用戶定義的聚合到得到這樣的結果。用戶定義的集合可以像用戶定義的集合一樣調用(例如SUM或MAX),它不使用遊標。

using System; 
using System.Data; 
using Microsoft.SqlServer.Server; 
using System.Data.SqlTypes; 
using System.IO; 
using System.Text; 

[Serializable()] 
[SqlUserDefinedAggregate(
    Format.UserDefined, 
    IsInvariantToNulls=true, 
    IsInvariantToDuplicates=false, 
    IsInvariantToOrder=false, 
    MaxByteSize=8000)] 
public class Concat : IBinarySerialize 
{ 
    #region Private fields 
    private string separator; 
    private StringBuilder intermediateResult; 
    #endregion 

    #region IBinarySerialize members 
    public void Read(BinaryReader r) 
    { 
     this.intermediateResult = new StringBuilder(r.ReadString()); 
    } 

    public void Write(BinaryWriter w) 
    { 
     w.Write(this.intermediateResult.ToString()); 
    } 
    #endregion 

    #region Aggregation contract methods 
    public void Init() 
    { 
     this.separator = ", "; 
     this.intermediateResult = new StringBuilder(); 
    } 

    public void Accumulate(SqlString pValue) 
    { 
     if (pValue.IsNull) 
     { 
      return; 
     } 

     if (this.intermediateResult.Length > 0) 
     { 
      this.intermediateResult.Append(this.separator); 
     } 
     this.intermediateResult.Append(pValue.Value); 
    } 

    public void Merge(Concat pOtherAggregate) 
    { 
     this.intermediateResult.Append(pOtherAggregate.intermediateResult); 
    } 

    public SqlString Terminate() 
    { 
     return this.intermediateResult.ToString(); 
    } 
    #endregion 
} 

this posts,你會發現的代碼,以及我調試我遇到的問題的解決方案。

我在生產環境中使用了這個聚合,它表現非常好。

+0

+1爲了好的答案。 :) – 2009-05-25 09:06:58

0

創建參數@ContactID一個標量函數和調用函數時,你通過它接觸式ID,您可以編寫輸出角色逗號分隔字符串的函數。

create function FetchProducts(@orderid int) returns varchar(1000) 
    as 
    begin 
    declare prods cursor for select ProductName from products where 
      productid in (select ProductId from [Order Details] 
       Where OrderId = @orderid) 

    open prods 

    declare @products varchar(1000) 
    declare @cp varchar(500) 
    Select @products = '' 
    fetch prods into @cp 

    while @@fetch_status = 0 
    begin 
     SET @products = @products + ',' + @cp 
     fetch prods into @cp 
    end 

    close prods 
    deallocate prods 

    return substring(@products, 2, len(@products)-1) 
    end 

現在你可以使用:如果您想獲取由客戶中您可以使用此代碼的特定順序排序的產品 然後調用這個函數在SELECT語句:)

例如功能如下:

select orderid, orderdate, dbo.FetchProducts(orderid) 
from orders where customerid = 'BERGS' 
1

編輯:從表改寫爲標量函數基於devio's idea所以如果你喜歡這篇文章投給他的答案。

如果CLR集成不是一個選項,你可以用一個標量函數實現這一點:

create function dbo.getRole(
    @ContactId int) 
returns varchar(8000) 
as 
begin 
declare @Roles varchar(8000) 

select 
    @Roles = case when @Roles is null then '' else @Roles + ', ' end + Role.Name 
from Role 
inner join ContactRole on Role.ID = ContactRole.RoleID 
where ContactRole.ContactID = @ContactID 

return @Roles 

然後,您可以調用這個函數來計算逗號分隔的列表爲每個聯繫人:

SELECT c.id, c.name, dbo.getRole(ID) as Roles 
FROM Contact 
2

您可以在單個查詢中完成,但我不知道性能是好還是壞。

SELECT [<group field 1>], [<group field 2>], [etc...], (
    SELECT CAST([<field to list>] AS VARCHAR(MAX)) + 
     CASE WHEN (ROW_NUMBER() OVER (ORDER BY [<inner order-by REVERSED>]) = 1) 
      THEN '' ELSE ',' END 
     AS [text()] 
    FROM [<inner table>] 
    WHERE [<inner table join field>] = [<outer table join field>] 
    AND [<inner conditions>] 
    ORDER BY [<inner order-by>] 
    FOR XML PATH('')) AS [<alias>] 
FROM [<outer table] 
WHERE [<outer conditions>] 

內部的CASE語句是剛剛從列表中刪除最後一個逗號 - 你必須爲了通過話了內部查詢,然後反向是ORDER BY在CASE語句。

6

僅僅因爲你使用SQL Server 2005(如果你是幸運的,有所有的XML設置正確設置),這裏是你簡單的SQL查詢(純SQL和無功能):

SELECT c.ID, c.Name, c.Address, 
    ( SELECT  r.Name + ',' 
     FROM  "ContactRole" cr 
     INNER JOIN "Role" r 
       ON cr.RoleID = r.ID 
     WHERE  cr.ContactID = c.ID 
     ORDER BY r.ID --r.Name 
     FOR XML PATH('') 
    ) AS "Roles" 
FROM "Contact" c 

要測試它爲你,只需要執行下面的整個片段:

WITH "Contact" (ID, Name, Address) AS (
       SELECT 1, 'p1-no role', NULL 
    UNION ALL SELECT 2, 'p2-one role', NULL 
    UNION ALL SELECT 3, 'p3-two roles', NULL 
) 
, "Role" (ID, Name)AS (
       SELECT 1, 'teacher' 
    UNION ALL SELECT 2, 'student' 
) 
, "ContactRole" (ContactID, RoleID) AS (
       SELECT 2, 1 
    UNION ALL SELECT 3, 1 
    UNION ALL SELECT 3, 2 
) 

SELECT c.ID, c.Name, c.Address, 
    ( SELECT  r.Name + ',' 
     FROM  "ContactRole" cr 
     INNER JOIN "Role" r 
       ON cr.RoleID = r.ID 
     WHERE  cr.ContactID = c.ID 
     ORDER BY r.ID --r.Name 
     FOR XML PATH('') 
    ) AS "Roles" 
FROM "Contact" c 

,你應該得到以下結果:

ID   Name   Address  Roles 
----------- ------------ ----------- ------------------ 
1   p1-no role NULL  NULL 
2   p2-one role NULL  teacher, 
3   p3-two roles NULL  teacher,student,