如何将true / false / unknown映射到-1 / 0 / null而不重复?

时间:2011-01-22 16:09:16

标签: sql sql-server sql-server-2005 tsql

我目前正在开发一个工具来帮助我的用户将他们的SQL代码移植到SQL-Server 2005.为此,我将SQL解析为语法树,分析它需要注意的构造,修改它并转换它回到T-SQL。

在我想支持的事情上,其他RDBMS的“bools is values too”语义。例如,MS-Access允许我编写select A.x and A.y as r from A,这在T-SQL中是不可能的,因为:

  1. 列不能具有布尔类型(列值不能和')
  2. 在预期表达式的地方不能使用逻辑谓词。
  3. 因此,我的转换例程将上述语句转换为:

    select case 
      when (A.x<>0) and (A.y<>0)
       then -1 
      when not((A.x<>0) and (A.y<>0)) 
       then 0 
      else 
       null 
      end as r 
     from A;
    

    哪个有效,但很烦人,因为我必须复制逻辑表达式(可能非常复杂或包含子查询等),以便区分truefalse和{{1 - 后者应映射为null。所以我想知道T-SQL专业人员是否知道更好的方法来实现这个目标?

    更新 我想指出,试图将操作数保持在整数域中的解决方案必须考虑到,一些操作数可能是第一位中的逻辑表达式。这意味着需要一个将bool转换为值的有效解决方案。例如:

    unknown

3 个答案:

答案 0 :(得分:1)

我认为没有一个好的答案,实际上它是SQL的限制。你可以为你需要的每个布尔表达式创建一个UDF

CREATE FUNCTION AndIntInt
(
    @x as int,@y as int
)
RETURNS int
AS
BEGIN

    if (@x<>0) and (@y<>0)
        return -1
    if not((@x<>0) and (@y<>0)) 
        return  0 
    return null
END

通过

使用
select AndIntInt(A.x,A.y) as r from A

答案 1 :(得分:1)

布尔处理

Access似乎使用给出2个布尔值的逻辑

  • 两者都必须是真的才能返回真实
  • 要么是false,要么返回false(不管空值)
  • 否则返回null

我不确定这是否是其他DBMS(Oracle,DB2,PostgreSQL)处理bool + null的方式,但这个答案是基于Access的确定(MySQL和SQLite同意)。结果表如下。

X      Y      A.X AND B.Y
0      0      0
0      -1     0
0      (null) 0
-1     0      0
-1     -1     -1
-1     (null) (null)
(null) 0      0
(null) -1     (null)
(null) (null) (null)

SQL Server帮助程序1:来自任何“单值”的布尔函数

在SQL Server中,此功能将填补缺少any value as boolean功能的空白。它返回一个三元结果,1/0 / null - 1和0是SQL Server等效的true / false(实际上不是布尔值)。

drop function dbo.BoolFromAny
GO
create function dbo.BoolFromAny(@v varchar(max)) returns bit as
begin
return (case
    when @v is null then null
    when isnumeric(@v) = 1 and @v like '[0-9]%' and (@v * 1.0 = 0) then 0
    else 1 end)
end
GO

注意:以Access作为起点,只有数值0的计算值为FALSE 这使用了一些SQL Server技巧

  1. 一切都可以转换为varchar。因此,只需要一个接受varchar输入的函数。
  2. isnumeric不全面,'。' isnumeric返回1,但在@v * 1.0处失败,因此需要对LIKE [0-9]%``进行显式测试以“修复”isnumeric。
  3. @v * 1.0需要克服一些算术问题。如果将字符串“1”传递给没有* 1.0的函数,它将弹出
  4. 现在我们可以测试这个功能了。

    select dbo.BoolFromAny('abc')
    select dbo.BoolFromAny(1)
    select dbo.BoolFromAny(0)  -- the only false
    select dbo.BoolFromAny(0.1)
    select dbo.BoolFromAny(-1)
    select dbo.BoolFromAny('')
    select dbo.BoolFromAny('.')
    select dbo.BoolFromAny(null)  -- the only null
    

    现在,您可以在针对任何单个列的查询中安全地使用它,例如

    SELECT dbo.BoolFromAny(X) = 1
    

    SQL Server帮助程序2:返回BOOL AND BOOL结果的函数

    现在,下一部分是在SQL Server中创建相同的真值表。此查询显示两位列如何交互,以及简单的CASE语句与Access生成相同的表以及更复杂的表。

    select a.a, b.a,
      case
      when a.a = 0 or b.a = 0 then 0
      when a.a = b.a then 1
      end
    from
    (select 1 A union all select 0 union all select null) a,
    (select 1 A union all select 0 union all select null) b
    order by a.a, b.a
    

    这很容易表达为函数

    create function dbo.BoolFromBits(@a bit, @b bit) returns bit as
    begin
      return case
        when @a = 0 or @b = 0 then 0
        when @a = @b then 1
        end
    end
    

    SQL Server转换其他表达式(不是单个值)

    由于缺乏对来自布尔位的转换的支持,SQL Server中已经[true / false / null]的表达式需要在CASE语句中重复。

    示例是SQL Server中的“true boolean”,它不能是列的结果。

    select A > B -- A=B resolves to one of true/false/null
    from C
    

    需要表达为

    select case when A is null or B is null then null when A > B then 1 else 0 end
    from C
    

    但是如果A不是标量值而是像(select sum(x)...)那样的子查询,那么你可以看到A将出现两次并在CASE语句中被重复两次(重复)。

    最终测试 现在我们将所有转换规则用于此长表达式

    SELECT X AND Y=Z AND C FROM ..
    ( assume X is numeric 5, and C is varchar "H" )
    ( note C contributes either TRUE or NULL in Access )
    

    这转换为SQL Server(链接两个函数并使用CASE)

    SELECT dbo.BoolFromBits(
           dbo.BoolFromBits(dbo.BoolFromAny(X), CASE WHEN Y=Z then 1 else 0 end),
                           dbo.BoolFromAny(C))
    FROM ...
    

    访问Bool or bool

    为了完整性,这是Access bool OR bool的真值表。基本上,它与AND相反,所以

    • 两者都必须为false才能返回false
    • 要么为true,要么返回true(无论空值)
    • 否则返回null

    因此,SQL SERVER案例语句将是

      case
      when a.a = 1 or b.a = 1 then 1
      when a.a = b.a then 0
      end
    

    (遗漏ELSE子句是故意的,因为省略时结果为NULL)

答案 2 :(得分:-1)

编辑:根据添加到问题中的其他信息以及对其中一个建议的答案所做的评论,我正在重新制定这个答案:

如果您移植到SQL Server,那么我希望您也在转换数据以匹配SQL Server类型。如果您有一个布尔字段,则True,False和Unknown将映射为1,0和NULL作为NULLable BIT字段。

考虑到这一点,您只需要担心转换纯布尔值。表达式如:

exists (select * from B where B.y=A.y)

A.x in (1,2,3)

已经处于可行的状态。意思,声明如下:

IF (EXISTS(SELECT 1 FROM Table))

IF (value IN (list))

已经正确了。所以你只需要担心“真实”的“1”本身就不够了。因此,您可以通过测试它们是否实际上等于“1”来将“1”值转换为布尔表达式。例如:

IF (value = 1)

相当于您之前所拥有的:

IF (value)

将所有这些放在一起,您应该能够简单地将旧代码的纯布尔值的所有实例转换为“value = 1”形式的布尔表达式,因为1将生成True,0将生成False和NULL会给你错误。

但是,真正的复杂性是通过WHERE条件选择值与测试不同。布尔表达式在WHERE条件中正确计算,但没有直接表示SELECT(特别是因为NULL / Unknown不是真正的布尔值)。因此,您可以在WHERE条件中使用“Value = 1”转换,但如果您希望将其作为结果进行选择,则仍需要CASE语句。

刚才提到过,由于NULL / Unknown不是真正的布尔值,因此为了WHERE条件的目的,尝试将“NULL AND NULL”转换为NULL是没有意义的。实际上,NULL确实为FALSE,因为它无法确定为TRUE。同样,在SELECT语句中,这可能与您的目的不同,这也是CASE语句是您唯一选择的原因。