CLR: Multi Param Aggregate, аргумент не в окончательном выводе - PullRequest
5 голосов
/ 31 мая 2010

Почему мой разделитель не появляется в конечном выводе? Он инициализирован как запятая, но я получаю только ~ 5 пробелов между каждым атрибутом, используя:

  SELECT [article_id]
         , dbo.GROUP_CONCAT(0, t.tag_name, ',') AS col
    FROM [AdventureWorks].[dbo].[ARTICLE_TAG_XREF] atx
    JOIN [AdventureWorks].[dbo].[TAGS] t ON t.tag_id = atx.tag_id
GROUP BY article_id

Бит для DISTINCT работает нормально, но работает в области Accumulate ...

Выход:

article_id  |  col
-------------------------------------------------
1           |  a         a         b         c         

Обновление: избыточный пробел между значениями вызван тем, что столбец определен как NCHAR (10), поэтому в выводе появятся 10 символов. Глупая ошибка с моей стороны ...

Решение


С помощью Мартина Смита по работе с методом Write(BinaryWriter w) это обновление работает для меня:

public void Write(BinaryWriter w)
{
    w.Write(list.Count);
    for (int i = 0; i < list.Count; i++ )
    {
        if (i < list.Count - 1)
        {
            w.Write(list[i].ToString() + delimiter);
        }
        else 
        {
            w.Write(list[i].ToString());
        }
    }
}

Вопрос:


Почему вышеизложенное решает мою проблему? И почему бы не позволить мне использовать более одного w.write вызова внутри цикла FOR?

C # код:


using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Xml.Serialization;
using System.Xml;
using System.IO;
using System.Collections;
using System.Text;

[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize = 8000)]
public struct GROUP_CONCAT : IBinarySerialize
{
    ArrayList list;
    string delimiter;

    public void Init()
    {
        list = new ArrayList();
        delimiter = ",";
    }

    public void Accumulate(SqlBoolean isDistinct, SqlString Value, SqlString separator)
    {
        delimiter = (separator.IsNull) ? "," : separator.Value ;

        if (!Value.IsNull)
        {
            if (isDistinct)
            {
                if (!list.Contains(Value.Value))
                {
                    list.Add(Value.Value);
                }
            }
            else
            {
                list.Add(Value.Value);
            }            
        }
    }

    public void Merge(GROUP_CONCAT Group)
    {
        list.AddRange(Group.list);
    }

    public SqlString Terminate()
    {
        string[] strings = new string[list.Count];

        for (int i = 0; i < list.Count; i++)
        {
            strings[i] = list[i].ToString();
        }

        return new SqlString(string.Join(delimiter, strings));
    }

    #region IBinarySerialize Members

    public void Read(BinaryReader r)
    {
        int itemCount = r.ReadInt32();
        list = new ArrayList(itemCount);

        for (int i = 0; i < itemCount; i++)
        {
            this.list.Add(r.ReadString());
        }
    }

    public void Write(BinaryWriter w)
    {
        w.Write(list.Count);
        foreach (string s in list)
        {
            w.Write(s);
        }
    }
    #endregion
}

Ответы [ 2 ]

5 голосов
/ 12 июня 2010

Проблема в том, что вы не сериализуете разделитель. Добавить:

w.Write(delimiter)

в качестве первой строки в вашем методе Write и

delimiter = r.ReadString();

как первая строка в вашем методе чтения.

Относительно ваших вопросов к предлагаемому обходному пути:

Почему вышеизложенное решает мою проблему?

Это не так. Он просто работал с вашим тестовым сценарием.

И почему бы мне не использовать более одного вызова w.write внутри цикла FOR?

Метод записи должен быть совместим с методом чтения. Если вы пишете две строки и читаете только одну, это не сработает. Идея в том, что ваш объект может быть удален из памяти, а затем загружен. Это то, что должны писать и читать. В вашем случае - это действительно происходило, и вы не смогли сохранить значение объекта.

0 голосов
/ 06 ноября 2014

Ответ от @agsamek правильный, но не полный. Обработчик запросов может создавать несколько агрегаторов, например, для параллельных вычислений и тому, который в конечном итоге будет содержать все данные после последовательных вызовов Merge(), может быть назначен пустой набор записей, то есть его метод Accumulate() может никогда не вызываться:

var concat1 = new GROUP_CONCAT();
concat1.Init();
results = getPartialResults(1); // no records returned
foreach (var result in results)
    concat1.Accumulate(result[0], delimiter); // never called
...
var concat2 = new GROUP_CONCAT();
concat2.Init();
results = getPartialResults(2);
foreach (var result in results)
    concat2.Accumulate(result[0], delimiter);
...
concat1.Merge(concat2);
...
result = concat1.Terminate();

В этом сценарии личное поле concat1 delimiter, используемое в Terminate(), остается тем же, что и по умолчанию в Init(), но не тем, которое вы передаете в SQL. К счастью или нет, ваш тестовый SQL использует то же значение разделителя, что и в Init(), так что вы не сможете увидеть разницу.

Я не уверен, является ли это ошибкой или она была исправлена ​​в более поздних версиях (я наткнулся на нее в SQL Server 2008 R2). Мой обходной путь должен был использовать другую группу, которая передана в Merge():

public void Merge(GROUP_CONCAT Group)
{
    if (Group.list.Count != 0) // Group's Accumulate() has been called at least once
    {

        if (list.Count == 0) // this Accumulate() has not been called
            delimiter = Group.delimiter;

        list.AddRange(Group.list);
    }
}

P.S. Я бы использовал StringBuilder вместо ArrayList.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...