Свести таблицу связей в столбец с несколькими значениями? - PullRequest
2 голосов
/ 20 марта 2012

У меня есть таблица только с идентификаторами товаров и идентификаторами категорий (товары могут быть в нескольких категориях). Как я могу свести идентификаторы категории в столбец продукта, чтобы я закончил с этим:

id | name | desc | categories
1 | test1 | lorem | 1,3,4,23
2 | test2 | ipsom | 4,6,24

Это похоже на то, что мне нужно перейти в отдельную таблицу для столбца категорий. Как я могу это сделать или есть лучший способ?

Ответы [ 4 ]

7 голосов
/ 20 марта 2012

Я создал агрегатную функцию CLR , которая принимает столбец varchar и возвращает все его значения, разделенные запятыми.Другими словами, он объединяет несколько строк в список через запятую. Я уверен, что его производительность намного лучше, чем у любого трюка T-Sql .

Как любая агрегатная функция, она может использоваться в комбинации с group by.Например:

SELECT id, name, desc, JoinStrings(CONVERT(VARCHAR(20), category_id))
FROM product p
INNER JOIN category_products c ON p.category_id = c.category_id
GROUP BY id, name, desc

Вот код C # для создания сборки CLR в Sql Server 2008:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;


[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToDuplicates=false, IsInvariantToOrder=false, IsInvariantToNulls=true, MaxByteSize=-1)]
public struct JoinStrings : IBinarySerialize
{
    private char[] sb;
    private int pos;
    public void Init()
    {
        sb = new char[512000];
        pos = 0;
    }

    public void Accumulate(SqlString Value)
    {
        if (Value.IsNull) return;
        char[] src = Value.ToString().ToCharArray();
        Array.Copy(src, 0, sb, pos, src.Length);
        pos += src.Length;
        sb[pos] = ',';
        pos++;
    }

    public void Merge(JoinStrings Group)
    {
        Accumulate(Group.Terminate());
    }

    public SqlString Terminate()
    {
        if (pos <= 0) 
            return new SqlString();
        else
            return new SqlString(new String(sb, 0, pos-1));
    }

    public void Read(System.IO.BinaryReader r)
    {
        this.Init();
        pos = r.ReadInt32();
        r.Read(sb, 0, pos);
    }

    public void Write(System.IO.BinaryWriter w)
    {
        w.Write(pos);
        w.Write(sb, 0, pos);
    }
}

Вот код для созданияфункция (хотя развертывание из Visual Studio должно делать это автоматически):

CREATE AGGREGATE [dbo].[JoinStrings]
(@s [nvarchar](4000))
RETURNS[nvarchar](max)
EXTERNAL NAME [YouAssemblyName].[JoinStrings]
1 голос
/ 20 марта 2012

Используйте функцию.
Это делает поиск текста, поэтому вам нужно будет адаптироваться.
КОАЛЕСС просто положить,.
Это из крупномасштабного производственного приложения - оно работает и работает быстро.
Функция была опрошена JustinPony, так как функция медленная
Я бью несколько таблиц с миллионами записей, но возвращаю только 100 строк.
Функция применяется только к сотням строк.

использование:

select top 5 sID, ( select [dbo].[JoinMVEnum](docSVsys.sID, '140') ) as [Flag Issue]
from docSVsys

функция

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[JoinMVText]
(
   @sID int,
   @fieldID tinyint
)
RETURNS VARCHAR(MAX)
AS 
BEGIN
    DECLARE @MVtextList varchar(max)
    SELECT @MVtextList = COALESCE(@MVtextList + '; ', '') + docMVtext.value
    FROM docMVtext with (nolock) 
    WHERE docMVtext.sID = @sID and fieldID = @fieldID
    RETURN @MVtextList
END

GO
1 голос
/ 20 марта 2012

Нет встроенного способа сделать это в MSSQL.

Имитация функции MySQL group_concat в Microsoft SQL Server 2005? содержит хорошее описание того, как реализовать обходной путь.

1 голос
/ 20 марта 2012

Я бы предложил использовать Рекурсивный CTE . Я считаю, что это было бы что-то вроде этого:

select productid, categoryid, 
    row_number() over (partition by id order by categoryid) as rownum
into #tabletorecurse
from TABLENAME

with finaloutput as
(
    select productid as id, name, desc, categoryid as categories, rownum
    from #tabletorecurse
        join PRODUCTTABLE
            on PRODUCTTABLE.id = #tabletorecurse.productid
    where rownum = 1

    union all

    select tr.id, tr.name, tr.desc, 
        finaloutput.categories + ', ' + tr.categoryid, tr.rownum
    from #tabletorecurse as tr
        join finaloutput 
            on finaloutput.rownum + 1 = tr.rownum 
                and finaloutput.id = tr.productid
)
select id, name, desc, categories
from finaloutput
    join 
    (
        select max(rownum) as maxrow, id
        from finaloutput
        group by id 
    ) as maxvalues
       on maxvalues.id = finaloutput.id 
           and maxvalues.maxrow = finaloutput.rownum
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...