c# - 当应用于大量数据时,SQL CLR 聚合无法正确终止

标签 c# sql-server t-sql sqlclr user-defined-aggregate

我已经创建并使用了很多次 SQL CLR 聚合,它用于连接值 - 它还按指定的数字对值进行排序,并使用用户输入分隔符来连接它们。

我在大量数据上使用了相同的聚合,并注意到未使用分隔符 - 值被连接但没有分隔符。

经过大量测试,我发现在Terminate方法中,分隔符丢失/未读取。我使用硬编码分隔符仔细检查了这一点 - 一切正常。

我猜想我的 ReadWrite 方法(在处理大量数据时使用)有问题,但无法理解是什么问题。

函数代码如下:

[Serializable]
[
    Microsoft.SqlServer.Server.SqlUserDefinedAggregate
    (
        Microsoft.SqlServer.Server.Format.UserDefined,
        IsInvariantToNulls = true,
        IsInvariantToDuplicates = false,
        IsInvariantToOrder = false,
        IsNullIfEmpty = false,
        MaxByteSize = -1
    )
]
/// <summary>
/// Concatenates <int, string, string> values defining order using the specified number and using the given delimiter
/// </summary>
public class ConcatenateWithOrderAndDelimiter : Microsoft.SqlServer.Server.IBinarySerialize
{
    private List<Tuple<int, string>> intermediateResult;
    private string delimiter;
    private bool isDelimiterNotDefined;

    public void Init()
    {
        this.delimiter = ",";
        this.isDelimiterNotDefined = true;
        this.intermediateResult = new List<Tuple<int, string>>();
    }

    public void Accumulate(SqlInt32 position, SqlString text, SqlString delimiter)
    {
        if (this.isDelimiterNotDefined)
        {
            this.delimiter = delimiter.IsNull ? "," : delimiter.Value;
            this.isDelimiterNotDefined = false;
        }

        if (!(position.IsNull || text.IsNull))
        {
            this.intermediateResult.Add(new Tuple<int, string>(position.Value, text.Value));
        }
    }

    public void Merge(ConcatenateWithOrderAndDelimiter other)
    {
        this.intermediateResult.AddRange(other.intermediateResult);
    }

    public SqlString Terminate()
    {
        this.intermediateResult.Sort();
        return new SqlString(String.Join(this.delimiter, this.intermediateResult.Select(tuple => tuple.Item2)));
    }

    public void Read(BinaryReader r)
    {
        if (r == null) throw new ArgumentNullException("r");

        int count = r.ReadInt32();
        this.intermediateResult = new List<Tuple<int, string>>(count);

        for (int i = 0; i < count; i++)
        {
            this.intermediateResult.Add(new Tuple<int, string>(r.ReadInt32(), r.ReadString()));
        }

        this.delimiter = r.ReadString();
    }

    public void Write(BinaryWriter w)
    {
        if (w == null) throw new ArgumentNullException("w");

        w.Write(this.intermediateResult.Count);

        foreach (Tuple<int, string> record in this.intermediateResult)
        {
            w.Write(record.Item1);
            w.Write(record.Item2);
        }

        w.Write(this.delimiter);
    }
}

最佳答案

仅当使用并行性并且特定组分布在多个线程上时,才会调用 Merge() 方法。在本例中,已调用 Init(),并且有 0 个或多个 Accumulate() 实例。

因此,在并行的情况下,如果已调用 Init() 但尚未调用 Accumulate(),则 delimiter 中的值 将是在 Init() 方法中设置的内容。问题中的代码显示它被设置为 ,,但我怀疑这是后来在试图弄清楚这一点时添加的。当然,这假设将逗号作为分隔符传入 Accumulate() 中。或者也许逗号总是在 Init() 中设置为默认值,但另一个字符是通过 Accumulate() 传入的,并且没有通过最终输出(问题中没有显示对 UDA 的具体调用,也没有显示错误的输出,因此这里存在一些歧义)。

虽然其他答案中显示的修复似乎有效,但它不是通用修复,因为可能存在当前对象至少调用过一次 Accumulate() 的情况,但是合并到这个对象中的“其他”对象仍然是空的(可能没有匹配的行,或者调用 Accumulate() 时值没有存储在本地的其他原因)。在这种情况下,当前对象将具有所需的分隔符,但“其他”对象仍将具有默认分隔符。理想的解决方案是将 isDelimiterNotDefined 的值存储在 Write() 方法中,然后在 Read() 中再次将其取出方法,并将本地值与 Merge() 方法中的 other.isDelimiterNotDefined 进行比较,以便确定是否应保留 delimiter 的本地值或其他值(取决于设置/定义哪一个)。

关于c# - 当应用于大量数据时,SQL CLR 聚合无法正确终止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51964069/

相关文章:

c# - 用户单击行链接按钮控件时如何获取中继器的项目行ID

c# - 如何检测/找到 while 循环的结束?

sql - 如何在列中找到连续的零?

c# - 数据表不接受 varbinary 的值

sql-server - float sql server 转换问题

sql-server-2005 - 动态 SQL Server 2005 枢轴

sql - 使用 INNER JOIN 获取 DISTINCT 记录

c# - 无法加载文件或程序集 System.Web.WebPages.Deployment

c# - log4net smtp appender 不发送电子邮件

sql-server - 如何加入到 "Other"行