2009-05-25 2 views
4

Я пишу функцию экспорта, где мне нужно экспортировать контакты в Excel, и я столкнулся с технической ошибкой - или, возможно, пробел в моих навыках SQL ближе к истине. ;)TSQL Comma Separation

Вот сценарий: У меня есть куча контактов в базе данных. Каждый контакт может иметь много разных ролей, например, контакт может быть как разработчиком C#, так и администратором баз данных, или администратором базы данных и IT-менеджером. Они разбиты на три таблицы:

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

Не слишком сложно следовать. Есть набор контактов и набор ролей. К ним присоединяется таблица ContactRole по соответствующим идентификаторам.

Когда я экспортирую контакты, мне нужно иметь столбец в экспорте со всеми разделяемыми запятыми ролями, например C# Developer, DBA или DBA, IT-manager. Экспорт будет сделан из кода ASP.NET/C#, поэтому я решил, что смогу сделать это в коде, если это произойдет, но у меня есть ощущение, что это можно сделать в 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 

обновление:

Приведенный выше код охватывает функциональные возможности для одного контакта. Вы можете создать скалярную функцию с параметром @ContactID и вызвать функцию из

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

Спасибо, это делает именно то, что я хочу! –

+1

У вас может быть низкая производительность для больших наборов результатов, поскольку SQL Server 2005 не обрабатывает подзапросы хорошо, поэтому вам следует отслеживать запросы с помощью этой функции. –

+0

Спасибо за голову, chopeen. Я ожидаю результатов в области 500-2000 строк, поэтому я уверен, что производительность не будет проблемой. Тем не менее, я буду следить за ним. –

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 за хороший ответ. :) –

0

Вы можете написать функцию, которая выводит роли как запятую строку, когда вы передаете его идентификатор контакта. Затем вызовите эту функцию в операторе отбора :)

Например, если вы хотите получать продукты, которые по заказу клиента в определенном порядке, вы можете использовать этот код:

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' 
1

EDIT: переписанный из таблицы скалярной функции на основе 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 
+0

+1 для хорошего и хорошо объясненного ответа , –

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>] 

Это саз внутри просто удалить последнюю запятую из списка - вы должны заказать что-то для внутреннего запроса, а затем обратный что 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,