【问题标题】:How to handle division by zero in DataTable.Compute()如何在 DataTable.Compute() 中处理除以零
【发布时间】:2021-09-28 15:35:39
【问题描述】:

我正在尝试构建一个模块,客户可以在其中创建自定义公式并根据他在 SQL 数据库中的数据进行计算。

客户可以使用基本算术运算符(+、-、*、/)和聚合函数(MIN、MAX、AVG、SUM)。此外,他还获得了一组列,他可以对其执行这些操作,例如:(UnitsProduced、ProductionTime、DownTime 等)。使用这些工具,他可以构建公式,例如:SUM(UnitsProduced)/SUM(ProductionTime)

在检查公式在数学上正确且仅包含有效列后,我使用存储过程从 SQL Server 将数据提取到 C# 中的 DataTable 中,然后对该公式使用 DataTable.Compute() 方法。

问题是当聚合函数产生 0 时。我尝试使用 IIF 条件填充提供的公式,以便 SUM(UnitsProduced)/SUM(ProductionTime) 变为 SUM(UnitsProduced)/IIF(SUM(ProductionTime)<>0,SUM(ProductionTime),NULL) 并且我希望得到 null 作为结果,但它给了我错误:

无法评估。表达式“System.Data.FunctionNode”不是聚合

在做了一些研究后,我发现我不能在条件语句中使用聚合函数。我还没有尝试过 LINQ,我不知道它是否会起作用。 我该如何解决这个问题?

【问题讨论】:

  • 要在 SQL 中处理它,您可以用 NULLIF 包装除数。
  • @Larnu 问题是我并不总是知道谁是除数,如果客户制作公式:SUM(ProductionTime+DownTime),则制作 NULLIF(ProductionTime, 0) 可能会导致错误。如果 ProductionTime 为 0,它将变为 NULL 并使公式的结果无效,尽管 DownTime 具有正值。
  • 您在上面声明用户正在创建公式,并且您正在解析它,所以它似乎应该在解析中更新除数。如果您发布进行解析的代码,我相信有人可以在那里提供帮助。
  • @Larnu 你的意思是将公式解析为:SUM(UnitsProduced)/NULLIF(SUM(ProductionTime),0)?如果是这样,那么它将无法工作,因为 Compute 不支持 NULLIF 根据:docs.microsoft.com/en-us/dotnet/api/…

标签: c# sql-server linq


【解决方案1】:

经过一番研究,我和我的同事得出了一个有趣的解决方案。

基本上,您需要找到给定公式的所有除数并创建一个过滤字符串,该字符串将过滤除除数为 0 的所有数据。此代码还处理多列的聚合,因为 Comute 无法做到这一点。

负责零除法和聚合的代码:

            DataTable dt = db.GetDataFromStoredProcedure("GetCustomFormulasData", param);
            //Avoid zero division
            List<string> divisors = GetFormulaDivisors(formula);
            string formulaFilter = "";
            foreach (string divisor in divisors)
                formulaFilter += $" and {divisor}<>0 ";
            if(formulaFilter != "")
                formulaFilter = formulaFilter.Remove(0, 5); //remove the 1st " and " 

            //Compute cant handle multiple columns in one aggregate, so we handle it here
            List<string> aggregations = GetFormulaAggregations(formula);
            for (int i = 0; i < aggregations.Count; i++)
            {
                string expr = aggregations[i].Substring(4, aggregations[i].Length - 5);//remove the agg func and last paren 
                DataColumn dc = new DataColumn
                {
                    DataType = typeof(float),
                    ColumnName = $"Column{i}",
                    Expression = expr
                };
                dt.Columns.Add(dc);
                Regex rgx = new Regex(Regex.Escape(expr));
                string temp = rgx.Replace(aggregations[i], dc.ColumnName);
                rgx = new Regex(Regex.Escape(aggregations[i]));
                formula = rgx.Replace(formula, temp);
                if (formulaFilter != "")
                    formulaFilter = rgx.Replace(formulaFilter, temp);
            }

            return float.Parse(dt.Compute(formula, formulaFilter).ToString());

GetDataFromStoredProcedure、GetFormulaDivisors、GetFormulaAggregations 是内部方法,顾名思义。

因此对于公式SUM(GoodUnitsProduced+RejectedUnits)/SUM(ProductionTime) 的示例,DataTable dt 将从存储过程中获取列:(GoodUnitsProduced, RejectedUnits, ProductionTime),列表除数将具有 SUM(ProductionTime),列表聚合将具有:SUM(GoodUnitsProduced+ RejectedUnits) 和 SUM(ProductionTime)。

在调用 Compute 方法之前,DataTable 将有两个额外的列,一个用于公式中的每个聚合,而过滤器将在其中包含除数。这样formula = SUM(Column0)/SUM(Column1)formulaFilter = SUM(Column1)&lt;&gt;0就解决了零除的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-13
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    • 2012-05-29
    相关资源
    最近更新 更多