SQL CLR 聚合在应用于大量数据时未正确终止
SQL CLR aggregate not terminating correctly when applied over huge amount of data
我创建并使用了很多次 SQL CLR 聚合,它连接值 - 它也按指定的数字对值进行排序,并使用用户输入分隔符连接它们。
我对大量数据使用了相同的聚合,并注意到未使用分隔符 - 值被连接但没有分隔符。
经过大量测试,发现在Terminate
方法中,分隔符是missing/not读取的。我使用硬编码分隔符仔细检查了这一点 - 一切正常。
我猜想我的 Read
和 Write
方法有问题(在处理大量数据时使用)但无法理解。
函数代码如下:
[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
方法中。它是:
public void Merge(ConcatenateWithOrderAndDelimiter other)
{
this.intermediateResult.AddRange(other.intermediateResult);
}
然后我将其更改为:
public void Merge(ConcatenateWithOrderAndDelimiter other)
{
this.intermediateResult.AddRange(other.intermediateResult);
this.delimiter = other.delimiter;
}
好像data是merge
的时候,分隔符没有初始化。我想在上面的上下文中,所有 this
属性都是空的。
无论如何,我不会接受这个作为答案,因为如果有人能够解释内部发生的事情,那将会很有帮助。
Merge()
方法仅在使用并行性并且特定组分布在多个线程时才会被调用。在这种情况下,Init()
已被调用,Accumulate()
.
的 0 个或多个实例
因此,在并行的情况下,如果 Init()
已被调用但尚未调用 Accumulate()
,则 delimiter
中的值将是Init()
方法。问题中的代码显示它被设置为 ,
,但我怀疑这是后来在试图解决这个问题时添加的。当然,这假设将逗号作为分隔符传入 Accumulate()
。或者也许逗号总是被设置为 Init()
中的默认值,但另一个字符是通过 Accumulate()
传入的,并且没有通过最终输出(对 UDA 的特定调用未显示在问题,也不是错误的输出,所以这里有些歧义。
虽然另一个答案中显示的修复似乎有效,但它不是通用修复,因为可能存在当前对象至少调用过一次 Accumulate()
的情况,但 "other" 合并到这个对象中的对象仍然是空的(可能没有匹配的行,或者调用 Accumulate()
时值未存储在本地的其他一些原因)。在这种情况下,当前对象将具有所需的分隔符,但 "other" 对象仍将具有默认值。理想的解决方案是在 Write()
方法中也存储 isDelimiterNotDefined
的值,在 Read()
方法中再次将其取回,并将本地值与 other.isDelimiterNotDefined
进行比较在 Merge()
方法中,以便您可以确定是否应该保留 delimiter
的本地值或其他值(取决于设置/定义的值)。
我创建并使用了很多次 SQL CLR 聚合,它连接值 - 它也按指定的数字对值进行排序,并使用用户输入分隔符连接它们。
我对大量数据使用了相同的聚合,并注意到未使用分隔符 - 值被连接但没有分隔符。
经过大量测试,发现在Terminate
方法中,分隔符是missing/not读取的。我使用硬编码分隔符仔细检查了这一点 - 一切正常。
我猜想我的 Read
和 Write
方法有问题(在处理大量数据时使用)但无法理解。
函数代码如下:
[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
方法中。它是:
public void Merge(ConcatenateWithOrderAndDelimiter other)
{
this.intermediateResult.AddRange(other.intermediateResult);
}
然后我将其更改为:
public void Merge(ConcatenateWithOrderAndDelimiter other)
{
this.intermediateResult.AddRange(other.intermediateResult);
this.delimiter = other.delimiter;
}
好像data是merge
的时候,分隔符没有初始化。我想在上面的上下文中,所有 this
属性都是空的。
无论如何,我不会接受这个作为答案,因为如果有人能够解释内部发生的事情,那将会很有帮助。
Merge()
方法仅在使用并行性并且特定组分布在多个线程时才会被调用。在这种情况下,Init()
已被调用,Accumulate()
.
因此,在并行的情况下,如果 Init()
已被调用但尚未调用 Accumulate()
,则 delimiter
中的值将是Init()
方法。问题中的代码显示它被设置为 ,
,但我怀疑这是后来在试图解决这个问题时添加的。当然,这假设将逗号作为分隔符传入 Accumulate()
。或者也许逗号总是被设置为 Init()
中的默认值,但另一个字符是通过 Accumulate()
传入的,并且没有通过最终输出(对 UDA 的特定调用未显示在问题,也不是错误的输出,所以这里有些歧义。
虽然另一个答案中显示的修复似乎有效,但它不是通用修复,因为可能存在当前对象至少调用过一次 Accumulate()
的情况,但 "other" 合并到这个对象中的对象仍然是空的(可能没有匹配的行,或者调用 Accumulate()
时值未存储在本地的其他一些原因)。在这种情况下,当前对象将具有所需的分隔符,但 "other" 对象仍将具有默认值。理想的解决方案是在 Write()
方法中也存储 isDelimiterNotDefined
的值,在 Read()
方法中再次将其取回,并将本地值与 other.isDelimiterNotDefined
进行比较在 Merge()
方法中,以便您可以确定是否应该保留 delimiter
的本地值或其他值(取决于设置/定义的值)。