在状态字段中获取空值,任何人都可以解释我做错了什么吗?

时间:2016-11-14 20:00:35

标签: sql sql-server

我想基于案例条件的状态字段,但我得到null。

Declare @Status NVarchar(20);
Set @Status = Case When Exists ( Select GunSerialNo
                                 From   dbo.ArmouryIssueGun
                                 Where  ModifiedOn != Null
                                        And CreatedOn != Null ) Then 'In Field'
                   When Exists ( Select GunSerialNo
                                 From   dbo.ArmouryIssueGun
                                 Where  ModifiedOn = Null
                                        And CreatedOn != Null ) Then 'In Armory'
              End;
   -- Insert statements for procedure here

Select  (Select BranchName
         From   Branch
         Where  BranchId = gun.BranchId
        ) As BranchName
      , gun.SerialNo As GunSerialNo
      , gun.GunType
      , gun.ModelNo
      , gun.GunId
      , Convert(Varchar(12), aig.CreatedOn, 103) IssueDate
      , Substring(Convert(Varchar(20), aig.CreatedOn, 9), 13, 5) + ' ' + Substring(Convert(Varchar(30), aig.CreatedOn, 9), 25, 2) As IssueTime
      , cl.LicenceHolderName As CarriedBy
      , (Select TypeName
         From   dbo.CommonValues
         Where  ID = aig.Purpose
        ) As Purpose
      , @Status As status
      , Convert(Varchar(12), aig.ModifiedOn, 103) As CollectedDate
      , Substring(Convert(Varchar(20), aig.ModifiedOn, 9), 13, 5) + ' ' + Substring(Convert(Varchar(30), aig.ModifiedOn, 9), 25, 2) As TimeIn
From    dbo.CarryAndUseLicence cl
Join    dbo.Branch b
        On b.BranchId = cl.BranchId
Join    dbo.Gun gun
        On cl.GunSerialNo = gun.SerialNo
Join    dbo.ArmouryIssueGun aig
        On aig.StaffId = cl.StaffId;

2 个答案:

答案 0 :(得分:4)

你的问题在这里:

Set @Status = Case When Exists ( Select GunSerialNo
                                 From   dbo.ArmouryIssueGun
                                 Where  ModifiedOn != Null
                                        And CreatedOn != Null ) Then 'In Field'
                   When Exists ( Select GunSerialNo
                                 From   dbo.ArmouryIssueGun
                                 Where  ModifiedOn = Null
                                        And CreatedOn != Null ) Then 'In Armory'
              End;

特别是这些部分:

 Where ModifiedOn != Null
 And CreatedOn != Null 

并且

Where ModifiedOn = Null
And CreatedOn != Null

NULL不能等于任何东西。甚至不是另一个NULL。因此,您无法使用=!=来比较它们。您需要使用IS NULLIS NOT NULL代替。

尝试将它们更改为以下内容:

 Where ModifiedOn Is Not Null
 And CreatedOn Is Not Null 

并且

Where ModifiedOn Is Null
And CreatedOn Is Not Null

答案 1 :(得分:4)

您的子查询没有相关条款!他们所做的只是查询查询表中的任何一行是否为空。

如果您对特定GunSerialNo的状态感兴趣,则必须对其进行查询,例如:

SELECT
FROM dbo.ArmouryIssueGun
WHERE
   ModifiedOn IS NOT NULL
   AND CreatedOn IS NOT Null
   AND GunSerialNo = @GunSerialNo -- A specific GunSerialNo
;

但是,我怀疑您对单个项目的状态不感兴趣。相反,您想知道一堆行的状态。在这种情况下,在@Status变量中存储单个值对您没有好处。您需要将其合并到一个查询中,并将子查询与外部查询关联起来(参见Status =表达式):

Select  (Select BranchName
      From   Branch
      Where  BranchId = gun.BranchId
     ) As BranchName
   , gun.SerialNo As GunSerialNo
   , gun.GunType
   , gun.ModelNo
   , gun.GunId
   , Convert(Varchar(12), aig.CreatedOn, 103) IssueDate
   , Substring(Convert(Varchar(20), aig.CreatedOn, 9), 13, 5) + ' ' + Substring(Convert(Varchar(30), aig.CreatedOn, 9), 25, 2) As IssueTime
   , cl.LicenceHolderName As CarriedBy
   , (Select TypeName
      From   dbo.CommonValues
      Where  ID = aig.Purpose
     ) As Purpose
   , Status =
      Case When Exists (
         Select *
         From dbo.ArmouryIssueGun aig
         Where
            gun.SerialNo = aig.GunSerialNo // correlate to outer query
            AND aig.ModifiedOn != Null
            AND aig.CreatedOn != Null
      ) Then 'In Field'
      When Exists (
         Select * 
         From dbo.ArmouryIssueGun aig
         Where
            gun.SerialNo = aig.GunSerialNo // correlate to outer query
            AND aig.ModifiedOn = Null
            AND aig.CreatedOn != Null
      ) Then 'In Armory'
      End
   , Convert(Varchar(12), aig.ModifiedOn, 103) As CollectedDate
   , Substring(Convert(Varchar(20), aig.ModifiedOn, 9), 13, 5) + ' ' + Substring(Convert(Varchar(30), aig.ModifiedOn, 9), 25, 2) As TimeIn
From    dbo.CarryAndUseLicence cl
Join    dbo.Branch b
     On b.BranchId = cl.BranchId
Join    dbo.Gun gun
     On cl.GunSerialNo = gun.SerialNo
Join    dbo.ArmouryIssueGun aig
     On aig.StaffId = cl.StaffId;

另外,请注意这些可以帮助您的其他评论:

  • 当您与NULL进行比较时,将无效以使用=!=<>。答案永远是NULL,这既不是假的,也不是真的。你无能为力将会发生任何其他事情。 x.Column != NULL将被视为假,NOT (x.Column != NULL)。您必须x.Column IS NULLx.Column IS NOT NULLNOT (x.Column IS NULL)

  • 似乎数据库设计可能存在严重的数据一致性错误。使用ModifiedOn作为检查枪是否已检出的代理完全不安全。该设计假设一条“快乐的道路”,在检查之前不能修改枪(很可能被违反),并且一旦修改后它就不能仍然在军械库中(例如,重新检入)。如果您没有立即使用某些实际状态列或其他机制来指示枪的当前状态,您的系统可能会出现严重的问题。

  • 如果ArmouryIssueGun表与每支枪都有一对一的关系,那么就会出现问题,除非在每种情况下,每支枪只能100%可靠地完全正确一次性发出。这似乎不太可能。 可以ArmouryIssueGun真的不是历史表,而是代表Gun表的一部分(就像C#中的部分类),因此是正确的1- to - (0 or)1。在这种情况下,我对使用ModifiedOn的评论肯定是正确的,因为这是一种完全不可靠的方式来判断枪是否已经检查到现场。

  • 如果关系是一对多的,因为我怀疑(并且应该如此),使用相当于历史日志表来检测某事物的当前状态通常不是最好的设计。可以犯错误。日志条目可能无法插入。应该有一个规范的地方,每个GunSerialNo在一行中。使用历史表可以让后来不知道您在此查询中使用的奇怪代理依赖关系的人认为简单地插入另一个ArmoryIssueGun行将解决问题,但您的查询假定只有ModifiedOn值证明该字段中当前是,而不仅仅是在过去某个时间点的字段

  • 每个表中的列名应该相同。您应该有一个名为SerialNo的列,另一个列的名称为GunSerialNo

  • 我可以看到枪的序列号如何为您的数据库创建一个相当不错的自然/业务键。但是,我怀疑是否存在对所有表格来说都不是一个好键的情况。一个序列号长多少个字符? int只使用4个字节,但如果您的序列号是20个字符,那么每行将占用序列号的5倍空间。请记住,聚集索引列在每个非聚集索引中都会重复。我不知道你的数据库的大小,但是如果你正在创建一个服务于整个国家军队的数据库,理论上可能有数百万条目!此时,性能确实开始变得重要,并且列使用的空间变得非常重要。

  • 两次加入同一张桌子是不理想的。您可以像这样更改上面的查询,使连接只发生一次,另外还允许SELECT子句甚至包括找到的最新行中的列:

    OUTER APPLY (
        SELECT TOP 1
           aig.*
        FROM
           dbo.ArmoryIssueGun aig
        WHERE
           gun.SerialNo = aig.GunSerialNo
        ORDER BY
           aig.ModifiedOn DESC
    ) aig
    
  • 也没有理由进行子查询,例如您为BranchName所做的子查询。它使查询混乱并且不一致。相反,一致地使用JOIN:

    LEFT JOIN Branch b
       ON gun.BranchId = b.BranchId
    

    然后,您可以在b.BranchName子句中使用SELECT,还可以从Branch表中提取其他列。根据我的经验,使用这样的子查询会导致某些思维习惯导致次优组织且难以理解的查询,有时甚至会产生负面的性能影响(例如,如果您想要Branch表中的另一列,那么只是投入了另一个子查询,你不必要地将获取数据的成本增加了一倍。)