如何为运行总函数的SQL CLR创建重置?

时间:2015-09-02 18:56:58

标签: c# sql .net sql-server sql-server-2008

我正在尝试创建一个SQL CLR函数,该函数执行类似于此处所解释的运行总计:SQL Server and fastest running totals using CLR – Updated

但是,此函数仅执行列中所有值的运行总计。我想要做的是重置运行总计,以便在field1(SQL varchar)和field2(SQL数字)之间转换时它变为0。但是,我似乎无法弄清楚执行此操作所需的代码。我尝试使用CallContext存储这两个字段,但我一直在运行null引用。我认为转换检测与运行总计算的工作方式类似,但事实并非如此。我一直绞尽脑汁待好几个小时没有运气。

这是我正在使用此功能的表,主键是REF_NO

CREATE TABLE [dbo].[USER_TB_TIME_TICKETS] ( [REF_NO] [dbo].[T_DOC_NO] NOT NULL, [REF_DAT] [dbo].[T_DAT_SMALL] NOT NULL, [ACT_DAT] [dbo].[T_DAT_SMALL] NOT NULL, [USR_ID] [dbo].[T_USR_ID] NOT NULL, [CUST_NO] [dbo].[T_CUST_NO] NOT NULL, [JOB_NO] [dbo].[USER_TB_T_JOB_NO] NOT NULL, [CODE] [dbo].[USER_TB_T_SERV_COD] NOT NULL, [HRS_WORKED] [dbo].[USER_TB_T_HOURS] NOT NULL, [DESCR] [varchar](200) NOT NULL, [MILEAGE] [dbo].[USER_TB_T_MILES] NULL, [NOTES] [dbo].[USER_TB_T_NOTE] NULL, [BILL_FLG] [varchar](1) NULL, [BILL_HRS] [decimal](6, 2) NULL, [EXCESS_HRS] [decimal](6, 2) NULL, )

这是SQL标量CLR函数包装器:

CREATE FUNCTION [dbo].[fn_RunningTotalDecimal_15_2_ResetStringNumeric]( @val [decimal](15, 2), @id [tinyint], @rowNo [int], @nullValue [decimal](15, 2), @field1 [nvarchar](15), @field2 [numeric](18, 0)) RETURNS [decimal](15, 2) WITH EXECUTE AS CALLER AS EXTERNAL NAME [SqlClrRunningTotals].[RDSAPI.SQLClrRunningTotals.RunningTotals].[RunningTotalDecimalResetStringNumeric]

这是C#代码:

/// <summary>
    /// Storage Structure for holding actual Total and row number for security check.
    /// </summary>
    /// <typeparam name="T">Totals Data Type</typeparam>
    private struct RtStorage<T> where T : struct
    {
        public T Total;
        public int RowNo;
    }

    private struct StringFieldStorage<T> where T : struct
    {
        public T stringField;    
    }

    private struct NumericFieldStorage<T> where T : struct
    {
        public T numericField;
    }

....Other extraneous class code...

 /// <summary>
    /// Calculates a running totals on Decimal data type based on transistion between a string and numeric field.
    /// </summary>
    /// <param name="val">Value of current row</param>
    /// <param name="id">ID of the function in single query</param>
    /// <param name="rowNo">Specifies expecter rowNo. It is for security check to ensure correctness of running totals</param>
    /// <param name="nullValue">Value to be used for NULL values</param>
    /// <param name="field1">String field</param>
    /// <param name="field2">Numeric field</param>
    /// <returns>SqlDecimal representing running total</returns>
    [SqlFunction(IsDeterministic = true)]
    public static SqlDecimal RunningTotalDecimalResetStringNumeric(SqlDecimal val, SqlByte id, int rowNo, SqlDecimal nullValue, SqlString field1, SqlDecimal field2)
    {
        string dataName = string.Format("MultiSqlRt_{0}", id.IsNull ? 0 : id.Value);
        string field1Name = string.Format("MultiSqlField1_{0}", id.IsNull ? 0 : id.Value);
        string field2Name = string.Format("MultiSqlField2_{0}", id.IsNull ? 0 : id.Value);

        object lastSum = CallContext.GetData(dataName);
        object field1Value = CallContext.GetData(field1Name);
        object field2Value = CallContext.GetData(field2Name);

        var storage = lastSum != null ? (RtStorage<SqlDecimal>)lastSum : new RtStorage<SqlDecimal>();
        storage.RowNo++;

        var stringFieldStorage = field1Value != null ? (StringFieldStorage<SqlString>)field1Value : new StringFieldStorage<SqlString>();
        var numericFieldStorage = field2Value != null ? (NumericFieldStorage<SqlDecimal>)field2Value : new NumericFieldStorage<SqlDecimal>();

        if (storage.RowNo != rowNo)
            throw new System.InvalidOperationException(string.Format("Rows were processed out of expected order. Expected RowNo: {0}, received RowNo: {1}", storage.RowNo, rowNo));

        if (stringFieldStorage.stringField != field1 || (stringFieldStorage.stringField == field1 && numericFieldStorage.numericField != field2))
        {
            storage.Total = new SqlDecimal(0);
            stringFieldStorage.stringField = field1;
            numericFieldStorage.numericField = field2;
        }

        if (!val.IsNull)
            storage.Total = storage.Total.IsNull ? val : storage.Total + val;
        else
            storage.Total = storage.Total.IsNull ? nullValue : (nullValue.IsNull ? storage.Total : storage.Total + nullValue);

        CallContext.SetData(dataName, storage);
        CallContext.SetData(field1Name, stringFieldStorage);
        CallContext.SetData(field2Name, numericFieldStorage);

        return storage.Total;
    }

修改

这是一张让它更清晰的图像。 (感兴趣的列被盯着。我会放一张图片,但我没有足够的代表。)

CUST_NO,JOB_NO,USR_ID,REF_NO,REF_DAT,ACT_DAT,HRS_WORKED,HOURS_QUOTED,BILL_FLG,BILL_HRS,EXCESS_HRS,running_total
ATA,1,AML,152364,2015-06-18,2015-06-16,0.25,12.00,Y,0.25,0.00,9.50
ATA,1,AMA,152367,2015-06-18,2015-06-18,0.25,12.00,Y,0.25,0.00,9.75
ATA,1,AML,152372,2015-06-18,2015-06-18,1.50,12.00,Y,1.50,0.00,11.25
ATA,1,AMA,152569,2015-06-22,2015-06-22,0.50,12.00,Y,0.50,0.00,11.75
ATA,1,AMA,152735,2015-06-25,2015-06-25,0.50,12.00,Y,0.25,0.25,12.25
**ATA**,**1**,AMA,153472,2015-07-14,2015-07-13,0.25,12.00,N,0.00,0.25,**12.50**
**ATA**,**2**,SCP,152097,2015-06-12,2015-06-10,0.50,3.00,Y,0.50,0.00,**13.00**
ATA,2,CTK,151923,2015-06-11,2015-06-11,0.75,3.00,Y,0.75,0.00,13.75
ATA,2,CTK,151998,2015-06-12,2015-06-12,0.75,3.00,Y,0.75,0.00,14.50

我要做的是更改C#代码,以便检测CUST_NO或JOB_NO之间的更改(如果CUST_NO相同),并在转换时将运行的总列重置为0。基本上,我想为每个CUST_NO为每个JOB_NO运行总计。我意识到这是一个分组功能,但是我正在使用CLR功能,因为在SQL中很难有效地计算运行总计,我将为每天增长的140,000条记录的表做这个。由于我使用的是SQL Server 2008 R2,因此我没有在2012年获得Window ROWS功能,所以我能够获得足够大的读数来运行报告这一数据的唯一方法就是使用CLR功能。本文将介绍性能测试:Best approaches for running totals – updated for SQL Server 2012

另一个编辑

我也愿意接受其他实施的建议。我目前有一个游标实现,但同样,它很慢。我已经计时它仍然执行超过30秒。

1 个答案:

答案 0 :(得分:0)

您无需重新编写CLR函数即可解决此问题,只需对数据进行正确分区即可。

dbo.fn_RunningTotalBigInt(BILL_HRS, JOB_NO, ROW_NUMBER() OVER(PARTITION BY CUST_NO,JOB_NO ORDER BY REF_DAT), null)