无法将空值传递给自定义聚合
Cannot Pass Null Value to Custom Aggregate
下午,
我正在编写一个自定义中值函数(不考虑现有的解决方案,我喜欢挑战),经过大量的摆弄之后,我已经完成了大部分工作。但是,我不能传入包含空值的列。我在 c# 代码中处理这个,但它似乎在到达那里之前被 SQL 停止了。
你收到这个错误...
Msg 6569, Level 16, State 1, Line 11 'Median' failed because parameter 1 is not allowed to be null.
C#:
namespace SQLMedianAggregate
{
[System.Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
Microsoft.SqlServer.Server.Format.UserDefined,
IsInvariantToDuplicates = false, // duplicates may change results
IsInvariantToNulls = true, // receiving a NULL is handled later in code
IsInvariantToOrder = true, // is sorted later
IsNullIfEmpty = true, // if no values are given the result is null
MaxByteSize = -1,
Name = "Median" // name of the aggregate
)]
public struct Median : IBinarySerialize
{
public double Result { get; private set; }
public bool HasValue { get; private set; }
public DataTable DT_Values { get; private set; } //only exists for merge essentially
public static DataTable DT_Final { get; private set; } //Need a static version so its accesible within terminate
public void Init()
{
Result = double.NaN;
HasValue = false;
DT_Values = new DataTable();
DT_Values.Columns.Add("Values", typeof(double));
DT_Final = new DataTable();
DT_Final.Columns.Add("Values", typeof(double));
}
public void Accumulate(double number)
{
if (double.IsNaN(number))
{
//skip
}
else
{
//add to tables
DataRow NR = DT_Values.NewRow();
NR[0] = number;
DT_Values.Rows.Add(NR);
DataRow NR2 = DT_Final.NewRow();
NR2[0] = number;
DT_Final.Rows.Add(NR2);
HasValue = true;
}
}
public void Merge(Median group)
{
// Count the product only if the other group has values
if (group.HasValue)
{
DT_Final.Merge(group.DT_Values);
//DT_Final = DT_Values;
}
}
public double Terminate()
{
if (DT_Final.Rows.Count == 0) //Just to handle roll up so it doesn't crash (doesnt actually work
{
DataRow DR = DT_Final.NewRow();
DR[0] = 0;
DT_Final.Rows.Add(DR);
}
//Sort Results
DataView DV = DT_Final.DefaultView;
DV.Sort = "Values asc";
DataTable DTF = new DataTable();
DTF = DV.ToTable();
////Calculate median and submit result
double MiddleRow = (DT_Final.Rows.Count -1.0) / 2.0;
if (MiddleRow % 2 != 0)
{
double upper = (double)(DT_Final.Rows[Convert.ToInt32(Math.Ceiling(MiddleRow))]["Values"]);
double lower = (double)(DT_Final.Rows[Convert.ToInt32(Math.Floor(MiddleRow))]["Values"]);
Result = lower + ((upper - lower) / 2);
} else
{
Result = (double)(DT_Final.Rows[Convert.ToInt32(MiddleRow)]["Values"]);
}
return Result;
}
public void Read(BinaryReader SerializationReader)
{
//Needed to get this working for some reason
}
public void Write(BinaryWriter SerializationWriter)
{
//Needed to get this working for some reason
}
}
}
SQL:
DROP AGGREGATE dbo.Median
DROP ASSEMBLY MedianAggregate
CREATE ASSEMBLY MedianAggregate
AUTHORIZATION dbo
FROM 'C:\Users\#######\Documents\Visual Studio 2017\Projects\SQLMedianAggregate\SQLMedianAggregate\bin\Debug\SQLMedianAggregate.dll'
WITH PERMISSION_SET = UNSAFE;
CREATE AGGREGATE dbo.Median (@number FLOAT) RETURNS FLOAT
EXTERNAL NAME [MedianAggregate]."SQLMedianAggregate.Median";
关于我缺少的设置或代码的任何想法都将允许这样做。我几乎只是想让它忽略空值。
SQL 版本是 SQL2008 R2 btw
问题出在您的数据类型上。您需要为 SQLCLR 参数、return 值和结果集列使用 Sql*
类型。在这种情况下,您需要更改:
Accumulate(double number)
进入:
Accumulate(SqlDouble number)
然后,您使用所有 Sql*
类型都具有的 Value
属性 访问 double
值(即本例中的 number.Value
)。
然后,在 Accumulate
方法的开头,您需要使用 IsNull
属性:
检查 NULL
if (number.IsNull)
{
return;
}
此外,有关一般使用 SQLCLR 的更多信息,请参阅我在 SQL Server Central 上就此主题撰写的系列文章:Stairway to SQLCLR(需要免费注册阅读该网站上的内容,但这是值得的 :-)。
而且,由于我们在这里讨论的是中值计算,请参阅我写的关于 UDA 和 UDT 主题的文章(也在 SQL Server Central 上)以中值为例:Getting The Most Out of SQL Server 2005 UDTs and UDAs.请记住,本文是为 SQL Server 2005 编写的,它对 UDT 和 UDA 的内存有 8000 字节的硬性限制。 SQL Server 2008 中取消了该限制,因此您无需使用该文章中所示的压缩技术,只需将 SqlUserDefinedAggregate
中的 MaxByteSize
设置为 -1
(就像您目前正在做)或 SqlMetaData.MaxSize
(或非常接近的事情)。
此外,DataTable
对于此类操作有点笨手笨脚。您只需要一个简单的 List<Double>
:-).
关于以下代码行(这里分为两行以防止需要滚动):
public static DataTable DT_Final { get; private set; }
//Need a static version so its accesible within terminate
这是对 UDA 和 UDT 工作原理的巨大误解。请不要在这里使用静态变量。静态变量在会话之间共享,因此您当前的方法是不是线程安全的。所以你要么会得到关于它已经被声明的错误,要么各种会话会改变其他会话不知道的值,因为它们都共享单个实例
DT_Final
。如果使用并行计划,错误 and/or 奇怪的行为(即无法调试的错误结果)可能会在单个会话中发生。
UDT 和 UDA 被序列化为存储在内存中的二进制值,然后被反序列化以保持其状态不变。这就是 Read
和 Write
方法的原因,也是您需要让这些方法起作用的原因。
同样,您不需要(或不想)DataTables
此处,因为它们使操作过于复杂并且占用了比理想情况更多的内存。请参阅我上面链接的关于 UDA 和 UDT 的文章,了解中值操作(以及一般的 UDA)应该如何工作。
下午,
我正在编写一个自定义中值函数(不考虑现有的解决方案,我喜欢挑战),经过大量的摆弄之后,我已经完成了大部分工作。但是,我不能传入包含空值的列。我在 c# 代码中处理这个,但它似乎在到达那里之前被 SQL 停止了。
你收到这个错误...
Msg 6569, Level 16, State 1, Line 11 'Median' failed because parameter 1 is not allowed to be null.
C#:
namespace SQLMedianAggregate
{
[System.Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
Microsoft.SqlServer.Server.Format.UserDefined,
IsInvariantToDuplicates = false, // duplicates may change results
IsInvariantToNulls = true, // receiving a NULL is handled later in code
IsInvariantToOrder = true, // is sorted later
IsNullIfEmpty = true, // if no values are given the result is null
MaxByteSize = -1,
Name = "Median" // name of the aggregate
)]
public struct Median : IBinarySerialize
{
public double Result { get; private set; }
public bool HasValue { get; private set; }
public DataTable DT_Values { get; private set; } //only exists for merge essentially
public static DataTable DT_Final { get; private set; } //Need a static version so its accesible within terminate
public void Init()
{
Result = double.NaN;
HasValue = false;
DT_Values = new DataTable();
DT_Values.Columns.Add("Values", typeof(double));
DT_Final = new DataTable();
DT_Final.Columns.Add("Values", typeof(double));
}
public void Accumulate(double number)
{
if (double.IsNaN(number))
{
//skip
}
else
{
//add to tables
DataRow NR = DT_Values.NewRow();
NR[0] = number;
DT_Values.Rows.Add(NR);
DataRow NR2 = DT_Final.NewRow();
NR2[0] = number;
DT_Final.Rows.Add(NR2);
HasValue = true;
}
}
public void Merge(Median group)
{
// Count the product only if the other group has values
if (group.HasValue)
{
DT_Final.Merge(group.DT_Values);
//DT_Final = DT_Values;
}
}
public double Terminate()
{
if (DT_Final.Rows.Count == 0) //Just to handle roll up so it doesn't crash (doesnt actually work
{
DataRow DR = DT_Final.NewRow();
DR[0] = 0;
DT_Final.Rows.Add(DR);
}
//Sort Results
DataView DV = DT_Final.DefaultView;
DV.Sort = "Values asc";
DataTable DTF = new DataTable();
DTF = DV.ToTable();
////Calculate median and submit result
double MiddleRow = (DT_Final.Rows.Count -1.0) / 2.0;
if (MiddleRow % 2 != 0)
{
double upper = (double)(DT_Final.Rows[Convert.ToInt32(Math.Ceiling(MiddleRow))]["Values"]);
double lower = (double)(DT_Final.Rows[Convert.ToInt32(Math.Floor(MiddleRow))]["Values"]);
Result = lower + ((upper - lower) / 2);
} else
{
Result = (double)(DT_Final.Rows[Convert.ToInt32(MiddleRow)]["Values"]);
}
return Result;
}
public void Read(BinaryReader SerializationReader)
{
//Needed to get this working for some reason
}
public void Write(BinaryWriter SerializationWriter)
{
//Needed to get this working for some reason
}
}
}
SQL:
DROP AGGREGATE dbo.Median
DROP ASSEMBLY MedianAggregate
CREATE ASSEMBLY MedianAggregate
AUTHORIZATION dbo
FROM 'C:\Users\#######\Documents\Visual Studio 2017\Projects\SQLMedianAggregate\SQLMedianAggregate\bin\Debug\SQLMedianAggregate.dll'
WITH PERMISSION_SET = UNSAFE;
CREATE AGGREGATE dbo.Median (@number FLOAT) RETURNS FLOAT
EXTERNAL NAME [MedianAggregate]."SQLMedianAggregate.Median";
关于我缺少的设置或代码的任何想法都将允许这样做。我几乎只是想让它忽略空值。
SQL 版本是 SQL2008 R2 btw
问题出在您的数据类型上。您需要为 SQLCLR 参数、return 值和结果集列使用 Sql*
类型。在这种情况下,您需要更改:
Accumulate(double number)
进入:
Accumulate(SqlDouble number)
然后,您使用所有 Sql*
类型都具有的 Value
属性 访问 double
值(即本例中的 number.Value
)。
然后,在 Accumulate
方法的开头,您需要使用 IsNull
属性:
NULL
if (number.IsNull)
{
return;
}
此外,有关一般使用 SQLCLR 的更多信息,请参阅我在 SQL Server Central 上就此主题撰写的系列文章:Stairway to SQLCLR(需要免费注册阅读该网站上的内容,但这是值得的 :-)。
而且,由于我们在这里讨论的是中值计算,请参阅我写的关于 UDA 和 UDT 主题的文章(也在 SQL Server Central 上)以中值为例:Getting The Most Out of SQL Server 2005 UDTs and UDAs.请记住,本文是为 SQL Server 2005 编写的,它对 UDT 和 UDA 的内存有 8000 字节的硬性限制。 SQL Server 2008 中取消了该限制,因此您无需使用该文章中所示的压缩技术,只需将 SqlUserDefinedAggregate
中的 MaxByteSize
设置为 -1
(就像您目前正在做)或 SqlMetaData.MaxSize
(或非常接近的事情)。
此外,DataTable
对于此类操作有点笨手笨脚。您只需要一个简单的 List<Double>
:-).
关于以下代码行(这里分为两行以防止需要滚动):
public static DataTable DT_Final { get; private set; }
//Need a static version so its accesible within terminate
这是对 UDA 和 UDT 工作原理的巨大误解。请不要在这里使用静态变量。静态变量在会话之间共享,因此您当前的方法是不是线程安全的。所以你要么会得到关于它已经被声明的错误,要么各种会话会改变其他会话不知道的值,因为它们都共享单个实例
DT_Final
。如果使用并行计划,错误 and/or 奇怪的行为(即无法调试的错误结果)可能会在单个会话中发生。
UDT 和 UDA 被序列化为存储在内存中的二进制值,然后被反序列化以保持其状态不变。这就是 Read
和 Write
方法的原因,也是您需要让这些方法起作用的原因。
同样,您不需要(或不想)DataTables
此处,因为它们使操作过于复杂并且占用了比理想情况更多的内存。请参阅我上面链接的关于 UDA 和 UDT 的文章,了解中值操作(以及一般的 UDA)应该如何工作。