我正在编写一个导出功能,我需要将联系人导出到Excel,而且我遇到了技术障碍 - 或者我的SQL技能差距可能更接近事实了。 ;)
以下是该方案: 我在数据库中有很多联系人。每个联系人都可以有许多不同的角色,例如联系人可以是C#Developer和DBA,也可以是DBA和IT-manager。这些分为三个表,如下所示:
------------------- ------------------- -------------------
* Contact * * ContactRole * * Role *
------------------- ------------------- -------------------
* ID * * ContactID * * ID *
* Name * * RoleID * * Name *
* Address * ------------------- -------------------
-------------------
不太难以遵循。有一组联系人和一组角色。它们由相应ID上的ContactRole表连接。
导出联系人时,我需要在导出中包含一个以逗号分隔的所有角色的列,例如C# Developer, DBA
或DBA, IT-manager
。
导出将从ASP.NET / C#代码隐藏完成,所以我想我可以在代码中执行此操作,但我觉得可以在SQL中执行此操作。
数据来自SQL Server 2005。
答案 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'