我已经为SQL Server 2014创建了一个CLR功能,它应该计算[Value]
列中第一个和最后一个值之间的减法。
这是表格:
Date_Time Value
-------------------------------------
2018-03-29 09:30:02.533 6771
2018-03-29 10:26:23.557 6779
2018-03-29 13:12:04.550 6787
2018-03-29 13:55:44.560 6795
以下是代码:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[SqlUserDefinedAggregate(Format.Native,
IsInvariantToDuplicates = false,
IsInvariantToNulls = true,
IsInvariantToOrder = false,
IsNullIfEmpty = true,
Name = "SUBT")]
public struct SUBT
{
private double first;
private double last;
private int count;
public void Init()
{
first = 0.0;
last = 0.0;
count = 0;
}
public void Accumulate(SqlDouble Value)
{
if (!Value.IsNull)
{
if (count == 0)
first = (double)Value;
else
last = (double)Value;
count += 1;
}
}
public void Merge(SUBT Group)
{
first = Group.first;
last = Group.last;
count += 1;
}
public SqlDouble Terminate()
{
double value = (double)(last - first);
return new SqlDouble(value);
}
}
所以结果应该是[Value]=24
,即6795 - 6771,但我得到6795 :(
错误在哪里?
答案 0 :(得分:0)
此聚合函数依赖于顺序,但聚合输入流没有排序保证。因此,结果取决于执行计划。
假设Date_Time
值是所需的排序,您可以同时提供Date_Time
和Value
作为函数参数,保存具有最低和最高Date_time
值的值,使用Merge
和Terminate
方法中的那些。
答案 1 :(得分:0)
这里有几个问题,我相信:
在Merge
方法中,您做出了两个错误的假设:
您假设传入的Group
有一个值,但它可以在一个或多个NULL
值上调用,在这种情况下,所有3个内部变量都是{{1} }。但是,您正在覆盖0
和first
的当前值,这些值在last
被调用之前可能具有非0
值,但最终将返回Merge
1}}由于被覆盖。
您假设至少有一个实例 - 当前实例或传入的0
- 已设置值(即,在非Group
值上至少调用过一次)。如果仅使用NULL
值调用这两个实例,则NULL
和0
将first
,但您将增加last
。我不确定合并后是否会再次调用counter
,但如果合并,则会跳过设置Accumulate
。目前这不是您的问题,因为您没有多个(或任何)first
值,但它是真实数据集的潜在问题。
如果两个实例都在非NULL
值上调用,则两者都至少设置NULL
,可能first
(或许不是)。通过使用传入的last
覆盖当前实例,您可能会丢失实际Group
值,从而失去真正的first
值。
正如@DanGuzman在他的回答中提到的,没有保证用户定义的聚合的排序(last
属性的IsInvariantToOrder
属性被忽略/未使用)。并且,正如他所指出的,您需要传递SqlUserDefinedAggregate
值才能手动处理此操作的这一方面。但是,它不会在Date_Time
方法中使用。它将用于比较两个新变量:Terminate
和firstDate
,分别初始化为未来和过去(这可能需要将lastDate
更改为Format
然后添加自定义UserDefined
和Read
方法 - 除非您可以将完整的Write
值存储为刻度,否则可能。)
DateTime
变量counter
方法中,您需要:
Accumulate
针对Date_Value
。如果它在firstDate
之前,则将此新值存储为firstDate
并将firstDate
存储为Value
,ELSE first
针对Date_Value
。如果它在lastDate
之后,则将此新值存储为lastDate
并将lastDate
存储为Value
,ELSE不执行任何操作last
方法中,对两个实例之间的Merge
进行类似的比较,并保留前一个(日期和值)。对firstDate
执行相同操作并保留后一个(日期和值)。 (注意:这些更改应解决上面#1)lastDate
问题
Merge
方法不应该改变为了它的价值,我完全按照您在问题中发布的方式运行代码,并使用以下测试查询返回预期值:
terminate
因此,如果您仍然遇到问题,那么您需要发布更多信息,例如您正在使用的测试查询(可能是表格)。但即使它似乎有效,就像在我的系统上一样,它仍然存在潜在的排序问题,无论如何都需要更新。
其他说明:
使用CREATE TABLE #Test ([Date_Time] DATETIME, [Value] FLOAT);
-- TRUNCATE TABLE #Test;
INSERT INTO #Test VALUES ('2018-03-29 09:30:02.533', 6771);
INSERT INTO #Test VALUES ('2018-03-29 10:26:23.557', 6779);
INSERT INTO #Test VALUES ('2018-03-29 13:12:04.550', 6787);
INSERT INTO #Test VALUES ('2018-03-29 13:55:44.560', 6795);
SELECT dbo.SUBT([Value])
FROM #Test;
-- 24
方法Accumulate
。无需转换传入参数。所有(double)Value
类型都具有Sql*
属性,该属性返回本机.NET类型中的值。在这种情况下,只需使用Value
即可。这对于可读性来说并不是很好,所以请考虑更改输入参数的名称; - )。
您永远不会使用Value.Value
的值,为什么要增加它?您可以改为使用counter
,并在此处将其设置为bool
。将每个非true
值设置为true将不会更改操作。但是,这是一个没有实际意义的点,因为您确实需要根据当前NULL
值在每个UDA调用中设置first
或last
。