找到一种聪明的方法来处理 Null 值。运营商覆盖可能吗?

Find a smart way to deal with Null value. Operator override possible?

我在 SQL Server 2008 数据库中有一个大标签公式 Table,一个数字数据 table。公式 table 就像

ID INT PRIMARY KEY, 
Formula VARCHAR(MAX), -- Nullable.

规则:

  1. 每个标签在公式中都有一个条目 table
  2. 如果公式不为空,则表示数据中没有值table
  3. 如果公式为空,则表示数据值来自数据table。在这种情况下,我称之为 'value' 标签。
  4. 在2的情况下,公式可能类似于T(123) + T(456),但永远不会像T(T(234)+T(456))。 T(ID)中的ID部分必须是一个常量。公式可以是+-*/(),并且可以有一些SQL函数。

数据table像

DATE SMALLDATETIME PRIMARY KEY, -- 2019-06-01
ID INT PRIMARY KEY,             -- Which ID this value belongs to
VALUE FLOAT                     -- Not Nullable

数据table规则

  1. 根据日期,并非所有值标记都在数据 Table 中具有值条目。
  2. DATE 和 ID 为主键

所以我写了一个 func ExtractTags,取一个 varchar(max) 和 return 2 个值的原始标签和提取的公式。

示例:

输入:

    'T(234) As T234, T(567) As T567'

输出:

RawTag:
    '[1],[3],[2],[6],[8],[10],[13],[467]'
ExtractedTags:
    '(([1] + [2]) * ([3] + [6]) - [8]) As T234, ([10] + [13] + [467]) As T567'

我生成的动态 SQL 看起来像

SELECT DATE, (([1] + [2]) * ([3] + [6]) - [8]) As T234, ([10] + [13] + [467]) As T567
FROM (SELECT N.DATE, N.Value, N.ID 
     FROM NumericData AS N 
         Where N.DATE BETWEEN '2019-05-01' And '2019-05-3'
) 
x PIVOT (
    MAX(Value) for ID in ([1],[3],[2],[6],[8],[10],[13],[467])
) p 

然后根据这2个值我可以创建一个动态的SQL,它将以数据table为中心,然后获取它的值,然后计算结果。

问题:

输出示例

如果我使用 IsNull([xxx],0) 我会看到

DATE      |  T234   |  T567
----------+---------+---------
2019-05-01|  0      |  0
2019-05-02| 123.5   |  0
2019-05-03| 456.5   |  567.5

如果我不使用 IsNull([xxx],0) 我会看到

DATE      |  T234   |  T567
----------+---------+---------
2019-05-01| NULL    |  NULL
2019-05-02| 123.5   |  NULL
2019-05-03| 456.5   |  567.5

好想看

DATE      |  T234   |  T567
----------+---------+---------
2019-05-01|  0      |  NULL
2019-05-02| 123.5   |  0
2019-05-03| 456.5   |  567.5

有什么解决这个问题的好办法吗? SQL 服务器可以覆盖 '+' 运算符吗?

如果我没理解错的话,你可以使用case表达式来做到这一点:

(case when [10] is not null or [13] is not null or [467] is not null
      then coalesce([10], 0) + coalesce([13], 0) + coalesce([467], 0)
 end)

但是,我不确定这是否适合您的处理结构。

我以为您可以将 0 结果转换回 NULL:

nullif(coalesce([10], 0) + coalesce([13], 0) + coalesce([467], 0), 0)

但显然你可以获得合法的零值。

我终于明白了。我创建了另一个名为 'SmartFormula' 的函数来处理这个问题。这是代码。

IF EXISTS (
    SELECT * FROM sysobjects WHERE id = object_id(N'SmartFormula') 
    AND xtype IN (N'FN', N'IF', N'TF')
)
    DROP FUNCTION SmartFormula
GO
CREATE FUNCTION SmartFormula 
(
    -- Add the parameters for the function here
    @Formula VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
    DECLARE @POS INT
    DECLARE @PEND INT
    DECLARE @RawTag VARCHAR(20)
    DECLARE @RawTags VARCHAR(MAX)
    DECLARE @RESULT VARCHAR(MAX)
    DECLARE @Field  VARCHAR(MAX)
    DECLARE @FldStart INT
    DECLARE @FldEnd INT

    SET @RESULT = ''
    SET @FldEnd = 0
    SET @FldStart = 1

    WHILE @FldStart < LEN(@Formula)
    BEGIN
        SET @FldEnd = CHARINDEX(',', @Formula, @FldStart)
        IF @FldEnd = 0 SET @FldEnd = LEN(@Formula)
        SET @Field = SUBSTRING(@Formula, @FldStart, @FldEnd - @FldStart + 1)
        SET @FldStart = @FldEnd + 1

        SET @RawTags = '';
        SET @POS = CHARINDEX('[', @Field, 1);
        WHILE @POS <> 0
        BEGIN
            SET @PEND = CHARINDEX(']', @Field, @POS);
            IF @PEND = 0 BREAK;
            SET @RawTag = SUBSTRING(@Field, @POS, @PEND - @POS + 1)
            IF CHARINDEX(@RawTag, @RawTags, 1) = 0  --Not in the tags yet
            BEGIN
                IF LEN(@RawTags) > 0
                    SET @RawTags = @RawTags + ' AND ' + @RawTag + ' Is NULL';
                ELSE
                    SET @RawTags = @RawTag + ' Is NULL';
            END
            SET @POS = CHARINDEX('[', @Field, @PEND + 1);
        END
        IF LEN(@RawTags) > 0
        BEGIN
            SEt @Field = REPLACE(@Field, 'AS', 'END AS')
            SET @Field = REPLACE(REPLACE(@Field, '[', 'ISNULL(['),']','],0)')
            SET @RESULT = @RESULT + 'CASE WHEN ' + @RawTags + ' THEN NULL ELSE ' + @Field
        END
    END
    RETURN @RESULT
END
GO

测试

    SELECT SmartFormula('([123] + [456]) / [235] As T12,([222] - [12345]) As T222')

输出

CASE WHEN [123] Is NULL AND [456] Is NULL AND [235] Is NULL THEN NULL ELSE (ISNULL([123],0) + ISNULL([456],0)) / ISNULL([235],0) END AS T12,CASE WHEN [222] Is NULL AND [12345] Is NULL THEN NULL ELSE (ISNULL([222],0) - ISNULL([12345],0)) END AS T222