SQL Server CREATE AGGREGATE逻辑错误

时间:2018-06-16 12:24:10

标签: sql-server aggregate-functions sqlclr

我已经为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 :(

错误在哪里?

2 个答案:

答案 0 :(得分:0)

此聚合函数依赖于顺序,但聚合输入流没有排序保证。因此,结果取决于执行计划。

假设Date_Time值是所需的排序,您可以同时提供Date_TimeValue作为函数参数,保存具有最低和最高Date_time值的值,使用MergeTerminate方法中的那些。

答案 1 :(得分:0)

这里有几个问题,我相信:

  1. Merge方法中,您做出了两个错误的假设:

    1. 您假设传入的Group有一个值,但它可以在一个或多个NULL值上调用,在这种情况下,所有3个内部变量都是{{1} }。但是,您正在覆盖0first的当前值,这些值在last被调用之前可能具有非0值,但最终将返回Merge 1}}由于被覆盖。

    2. 您假设至少有一个实例 - 当前实例或传入的0 - 已设置值(即,在非Group值上至少调用过一次)。如果仅使用NULL值调用这两个实例,则NULL0first,但您将增加last。我不确定合并后是否会再次调用counter,但如果合并,则会跳过设置Accumulate。目前这不是您的问题,因为您没有多个(或任何)first值,但它是真实数据集的潜在问题。

    3. 如果两个实例都在非NULL值上调用,则两者都至少设置NULL,可能first(或许不是)。通过使用传入的last覆盖当前实例,您可能会丢失实际Group值,从而失去真正的first值。

  2. 正如@DanGuzman在他的回答中提到的,没有保证用户定义的聚合的排序(last属性的IsInvariantToOrder属性被忽略/未使用)。并且,正如他所指出的,您需要传递SqlUserDefinedAggregate值才能手动处理此操作的这一方面。但是,它不会在Date_Time方法中使用。它将用于比较两个新变量:TerminatefirstDate,分别初始化为未来和过去(这可能需要将lastDate更改为Format然后添加自定义UserDefinedRead方法 - 除非您可以将完整的Write值存储为刻度,否则可能。)

    • 摆脱DateTime变量
    • counter方法中,您需要:
      1. 如果传入的Accumulate针对Date_Value。如果它在firstDate之前,则将此新值存储为firstDate并将firstDate存储为Value,ELSE
      2. 如果传入的first针对Date_Value。如果它在lastDate之后,则将此新值存储为lastDate并将lastDate存储为Value,ELSE不执行任何操作
    • last方法中,对两个实例之间的Merge进行类似的比较,并保留前一个(日期和值)。对firstDate执行相同操作并保留后一个(日期和值)。 (注意:这些更改应解决上面#1)
    • 中提到的所有lastDate问题
    • Merge方法不应该改变
  3. 为了它的价值,我完全按照您在问题中发布的方式运行代码,并使用以下测试查询返回预期值:

    terminate

    因此,如果您仍然遇到问题,那么您需要发布更多信息,例如您正在使用的测试查询(可能是表格)。但即使它似乎有效,就像在我的系统上一样,它仍然存在潜在的排序问题,无论如何都需要更新。

    其他说明:

    1. 使用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即可。这对于可读性来说并不是很好,所以请考虑更改输入参数的名称; - )。

    2. 您永远不会使用Value.Value的值,为什么要增加它?您可以改为使用counter,并在此处将其设置为bool。将每个非true值设置为true将不会更改操作。但是,这是一个没有实际意义的点,因为您确实需要根据当前NULL值在每个UDA调用中设置firstlast