代码之家  ›  专栏  ›  技术社区  ›  Ollie

无法将Null值传递给自定义聚合

  •  2
  • Ollie  · 技术社区  · 7 年前

    下午

    Msg 6569, Level 16, State 1, Line 11 'Median' failed because parameter 1 is not allowed to be null.
    

     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

    1 回复  |  直到 7 年前
        1
  •  2
  •   Solomon Rutzky    7 年前

    Sql* SQLCLR参数、返回值和结果集列的类型。在这种情况下,您需要更改:

    Accumulate(double number)
    

    进入:

    Accumulate(SqlDouble number)
    

    double 使用 Value 所有 Sql* 类型有(即。 number.Value 在这种情况下)。

    Accumulate 方法,您需要检查 NULL 使用 IsNull 属性:

    if (number.IsNull)
    {
      return;
    }
    

    此外,有关一般使用SQLCLR的更多信息,请参阅我在SQL Server Central上就此主题编写的系列文章: Stairway to SQLCLR (阅读该网站的内容需要免费注册,但值得:-)。

    而且,由于我们在这里讨论的是中值计算,请参阅我(也在SQL Server Central上)撰写的关于UDA和UDT主题的文章,其中使用中值作为示例: Getting The Most Out of SQL Server 2005 UDTs and UDAs MaxByteSize SqlUserDefinedAggregate -1 (如您当前所做)或 SqlMetaData.MaxSize (或者与之非常接近的东西)。

    而且 DataTable List<Double> :-).


    关于以下代码行(此处分为2行,以防止需要滚动):

    public static DataTable DT_Final { get; private set; }
       //Need a static version so its accesible within terminate
    

    DT_Final 。如果使用并行计划,则错误和/或异常行为(即无法调试的错误结果)可能会在单个会话中发生。

    UDT和UDA被序列化为存储在内存中的二进制值,然后被反序列化,从而保持其状态不变。这就是 Read Write 方法,以及为什么需要让这些方法发挥作用。

    再说一次,你不需要(或想要) DataTables 在这里,他们的操作过于复杂,占用了比理想情况更多的内存。请参阅我在上面链接的关于UDAs和UDT的文章,以了解中值运算(以及一般的UDAs)应该如何工作。