找到一种聪明的方法来处理 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.
规则:
- 每个标签在公式中都有一个条目 table
- 如果公式不为空,则表示数据中没有值table
- 如果公式为空,则表示数据值来自数据table。在这种情况下,我称之为 'value' 标签。
- 在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规则
- 根据日期,并非所有值标记都在数据 Table 中具有值条目。
- 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为中心,然后获取它的值,然后计算结果。
问题:
- 由于某些值在某个日期可能没有条目,所以它可能显示为Null。例如,([10] + [13] + [467]) ==> T567。如果 [467] 没有值,则结果为空。在这种情况下,我想忽略 [467] 的值,或者将其处理为 0.
- 如果我把[xxx]全部改成IsNull([xxx], 0),又会出现一个问题:说[10],[13],[467]都是null,那我要看T567为空。
- 我正在考虑创建自己的函数 SUMIFNOTNULL(a,b) ==> 如果 a 和 b 都为 null,则只有 returns null,否则将 null 处理为 0。
- 但问题是 a) 我不允许更改公式 table,并且
它可能会继续增长。 b) 即使我可以,改变也将是一项巨大的工作
所有公式(超过 10,000 条记录)
输出示例
如果我使用 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
我在 SQL Server 2008 数据库中有一个大标签公式 Table,一个数字数据 table。公式 table 就像
ID INT PRIMARY KEY,
Formula VARCHAR(MAX), -- Nullable.
规则:
- 每个标签在公式中都有一个条目 table
- 如果公式不为空,则表示数据中没有值table
- 如果公式为空,则表示数据值来自数据table。在这种情况下,我称之为 'value' 标签。
- 在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规则
- 根据日期,并非所有值标记都在数据 Table 中具有值条目。
- 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为中心,然后获取它的值,然后计算结果。
问题:
- 由于某些值在某个日期可能没有条目,所以它可能显示为Null。例如,([10] + [13] + [467]) ==> T567。如果 [467] 没有值,则结果为空。在这种情况下,我想忽略 [467] 的值,或者将其处理为 0.
- 如果我把[xxx]全部改成IsNull([xxx], 0),又会出现一个问题:说[10],[13],[467]都是null,那我要看T567为空。
- 我正在考虑创建自己的函数 SUMIFNOTNULL(a,b) ==> 如果 a 和 b 都为 null,则只有 returns null,否则将 null 处理为 0。
- 但问题是 a) 我不允许更改公式 table,并且 它可能会继续增长。 b) 即使我可以,改变也将是一项巨大的工作 所有公式(超过 10,000 条记录)
输出示例
如果我使用 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