TSQL逗号分隔

时间:2009-05-25 08:01:30

标签: sql sql-server tsql

我正在编写一个导出功能,我需要将联系人导出到Excel,而且我遇到了技术障碍 - 或者我的SQL技能差距可能更接近事实了。 ;)

以下是该方案: 我在数据库中有很多联系人。每个联系人都可以有许多不同的角色,例如联系人可以是C#Developer和DBA,也可以是DBA和IT-manager。这些分为三个表,如下所示:

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

不太难以遵循。有一组联系人和一组角色。它们由相应ID上的ContactRole表连接。

导出联系人时,我需要在导出中包含一个以逗号分隔的所有角色的列,例如C# Developer, DBADBA, IT-manager。 导出将从ASP.NET / C#代码隐藏完成,所以我想我可以在代码中执行此操作,但我觉得可以在SQL中执行此操作。

数据来自SQL Server 2005。

7 个答案:

答案 0 :(得分: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,

答案 1 :(得分: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中,您将找到代码以及我遇到的调试问题的解决方案。

我在生产环境中使用了这个聚合,并且表现非常好。

答案 2 :(得分: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 

更新

以上代码涵盖单个联系人的功能。您可以使用参数@ContactID创建标量函数,并从

调用该函数
Select Name, dbo.GetContactRoles(ID) From Contact

答案 3 :(得分: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语句中反向 ORDER BY。

答案 4 :(得分: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 :(得分: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

答案 6 :(得分:0)

您可以编写一个函数,在传递联系人ID时将角色输出为逗号分隔字符串。 然后在select语句中调用此函数:)

例如,如果您想要获取客户按特定订单订购的产品,您可以使用以下代码:

    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 orderid, orderdate, dbo.FetchProducts(orderid) 
from orders where customerid = 'BERGS'