【问题标题】:Optimize access query with nested IIF's使用嵌套的 IIF 优化访问查询
【发布时间】:2011-07-08 21:47:10
【问题描述】:

有没有更好的方法在我的 where 子句中编写以下内容?

WHERE (IIf([GrpOrder]=3,IIf([LabelText]="Totals",True,False),True)) =True))

谢谢,

斯科特

【问题讨论】:

    标签: sql ms-access


    【解决方案1】:

    我假设您的代码包含拼写错误(非空白括号),实际上应该是:

    WHERE IIf([GrpOrder]=3,IIf([LabelText]="Totals",True,False),True) = true
    

    从 SQL 代码的角度来看,实际上有九种情况需要考虑,因为 SQL 的三值逻辑与NULL

    GrpOrder = 3
    GrpOrder <> 3
    GrpOrder IS NULL
    
    LabelText = 'Totals'
    LabelText <> 'Totals'
    LabelText IS NULL
    

    组合起来有九种情况,例如测试数据和结果:

    OP_result | GrpOrder | LabelText  
    ----------------------------------
         TRUE |       55 | 'Totals'
         TRUE |       55 | 'Tallies'
         TRUE |       55 | <NULL>
         TRUE |        3 | 'Totals'
        FALSE |        3 | 'Tallies'
        FALSE |        3 | <NULL>
         TRUE |   <NULL> | 'Totals'
         TRUE |   <NULL> | 'Tallies'
         TRUE |   <NULL> | <NULL>
    

    最安全的方法是写出一系列OR 子句,为每个OR 子句的两列明确处理NULL。但是,这是非常冗长的,最好将这两个返回 FALSE 的情况进行标记。这就是大多数人(包括我!)遇到 NULL 问题的地方:这太违反直觉了!

    例如,很想这样写:

    (GrpOrder = 3 AND LabelText IS NULL)
    OR
    (GrpOrder = 3 AND LabelText <> 'Totals')
    

    然后使用NOT“翻转”它的值:

    NOT (
         (GrpOrder = 3 AND LabelText IS NULL)
         OR
         (GrpOrder = 3 AND LabelText <> 'Totals')
        )
    

    但是,这样做NULL 会潜入结果集:

    OP_result | attempt_1 | GrpOrder | LabelText  
    ---------------------------------------------
         TRUE |      TRUE |       55 | 'Totals'
         TRUE |      TRUE |       55 | 'Tallies'
         TRUE |      TRUE |       55 | <NULL>
         TRUE |      TRUE |        3 | 'Totals'
        FALSE |     FALSE |        3 | 'Tallies'
        FALSE |     FALSE |        3 | <NULL>
         TRUE |      TRUE |   <NULL> | 'Totals'
         TRUE |    <NULL> |   <NULL> | 'Tallies'
         TRUE |    <NULL> |   <NULL> | <NULL>
    

    因此,我们需要明确处理比乍看之下更多的案例。

    我能想出的最简单的谓词可以在 Access 中给出所需的结果:

    NOT
    (
     (LabelText <> 'Totals' OR LabelText IS NULL)
     AND GrpOrder = 3 
     AND GrpOrder IS NOT NULL
    )
    

    [...读起来很奇怪,我想知道 OP 的代码是否首先产生了预期的结果。]

    要学习的主要课程:

    • 应避免在 SQL 中使用 NULL:它是违反直觉的,即使是非常有经验的 SQL 编码人员也会导致错误。
    • 始终发布具有预期结果的架构(例如CREATE TABLESQL DDL...)和示例数据(...例如INSERT INTOSQL DML...)(...或在必要时使用文字和图片;) 因为如果你的列被标记为NOT NULL 那么答案就简单多了! :)

    @Yanir Kleiman cmets:

    GrpOrder 不能为 3 和 NULL 同时,所以检查它不为空 在这种情况下是多余的

    这样想是可以原谅的。但这是 Access :) 对于声称符合 SQL 标准的 SQL 产品,我们有出色的规范。 Access 声称没有这样的合规性和the documentation the Access Team have provided is of a particularly low quality

    相反,在 Access-land 中,要使某件事属实,您必须对其进行实际测试!

    当我删除谓词时

    AND GrpOrder IS NOT NULL
    

    空值出现在结果集中。虽然这感觉像是“违背逻辑”,但请记住,SQL 的三值逻辑仅在 Access 声称不符合规范的规范中定义。如果访问团队没有告诉我们产品应该如何工作,我们如何判断以上是错误还是功能?即使我们可以说服他们这是一个错误,他们会修复它吗?

    下面我提供 VBA 代码来重现该问题:只需复制+粘贴到任何 VBA 模块中,无需设置引用。它在 temp 文件夹中创建一个新的 .mdb,然后创建表和测试数据。无需在机器上安装 Access,例如使用 Excel 的 VBA 编辑器。

    消息框显示分别包含和删除上述谓词时的结果集。除了两个表列之外,还有两个计算列显示值为 -1 (TRUE)、0 (FALSE) 和 NULL,最左边的一个是 OP:

    Sub AccessStrangeLogic()
    
      On Error Resume Next
      Kill Environ$("temp") & "\DropMe.mdb"
      On Error GoTo 0
    
      Dim cat
      Set cat = CreateObject("ADOX.Catalog")
      With cat
        .Create _
            "Provider=Microsoft.Jet.OLEDB.4.0;" & _
            "Data Source=" & _
            Environ$("temp") & "\DropMe.mdb"
        With .ActiveConnection
    
          Dim Sql As String
          Sql = _
          "CREATE TABLE GrpOrders" & vbCr & _
          "(" & vbCr & _
          " GrpOrder INTEGER," & vbCr & _
          " LabelText NVARCHAR(10)" & vbCr & _
          ");"
          .Execute Sql
    
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (55, 'Totals');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (55, 'Tallies');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (55, NULL);"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (3, 'Totals');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (3, 'Tallies');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (3, NULL);"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (NULL, 'Totals');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (NULL, 'Tallies');"
          .Execute Sql
          Sql = _
          "INSERT INTO GrpOrders (GrpOrder, LabelText)" & _
          " VALUES (NULL, NULL);"
          .Execute Sql
    
          ' Include "AND GrpOrder IS NOT NULL"
          Sql = _
          "SELECT *, " & vbCr & _
          "       IIf([GrpOrder]=3,IIf([LabelText]=""Totals"",True,False),True) = true AS OP_result, " & vbCr & _
          "       NOT" & vbCr & _
          "       (" & vbCr & _
          "        (LabelText <> 'Totals' OR LabelText IS NULL)" & vbCr & _
          "        AND GrpOrder = 3 " & vbCr & _
          "        AND GrpOrder IS NOT NULL" & vbCr & "       )" & vbCr & _
          "  FROM GrpOrders" & vbCr & _
          " ORDER " & vbCr & _
          "    BY GrpOrder DESC, LabelText DESC;"
    
          Dim rs
          Set rs = .Execute(Sql)
    
          ' Remove "AND GrpOrder IS NOT NULL"
          Sql = Replace$(Sql, "AND GrpOrder IS NOT NULL", "")
    
          Dim rs2
          Set rs2 = .Execute(Sql)
    
          MsgBox _
              "Include 'AND GrpOrder IS NOT NULL':" & vbCr & _
              rs.GetString(, , vbTab, vbCr, "<NULL>") & vbCr & _
              "remove 'AND GrpOrder IS NOT NULL':" & vbCr & _
              rs2.GetString(, , vbTab, vbCr, "<NULL>")
    
    
        End With
        Set .ActiveConnection = Nothing
      End With
    End Sub
    

    【讨论】:

    • 具有讽刺意味的是,您错过了这样一个事实,即使用 NOT 您已经解决了 GrpOrder = NULL 的情况:在这种情况下,GrpOrder = 3 将返回 false,因此整个事情将返回 true - 所以您可以删除“GrpOrder IS NOT NULL”谓词。另一种看待它的方式:GrpOrder 不能同时为 3 和 NULL,所以在这种情况下检查它不是 null 是多余的。
    • @Yanir Kleiman:“GrpOrder 不能同时为 3 和 NULL,因此在这种情况下检查它不是 null 是多余的”——你错了,我可以证明 :)请参阅此答案的更新。
    • “避免 Null”的建议很糟糕。几乎是你写过的最糟糕的东西。
    • 其次,期望 NULL 的行为不同于 NULL 的设计行为是人们遇到问题的主要原因。他们希望可以将其与已知值进行比较。这只是飞行员错误,不是 NULL 的问题,它在逻辑上必须按照定义的方式运行。
    • Null 更好地代表真实世界的数据。它比涉及制造虚假数据的替代方案更容易使用。我已经完成了。
    【解决方案2】:

    首先,第二个 IIF 是多余的 - “IIF(X, True, False)” 总是可以替换为“X”。

    除此之外,选择的逻辑是“where GrpOrder = 3 and LabelText="Totals", OR GrpOrder 3”。

    这与“where LabelText="Totals" OR GrpOrder 3”相同,因此:

    WHERE [GrpOrder] <> 3 OR [LabelText]="Totals"
    

    *我不记得访问是否使用 或 != 来表示不等式,所以无论哪个有效。


    编辑:

    我们总共有4个案例:

    GrpOrder = 3 和 LabelText = "Totals" => 接受

    GrpOrder = 3 和 LabelText "Totals" => 不接受

    GrpOrder 3 和 LabelText = "Totals" => 接受

    GrpOrder 3 和 LabelText "Totals" => 接受

    我们不接受的唯一情况是当 GrpOrder = 3 且 LabelText "Totals" 时,这与我们接受 GrpOrder 3(底部两行)或 LabelText="Totals" 的所有行相同(第一行和第三行)。第 2 行是唯一不被接受的。

    【讨论】:

    • 我不确定这是否正确。我对这些嵌套 iif 的解释是,他们试图说“给我所有记录,但以下例外。我不想要 GrpOrder=3 的任何记录,除非 GrpOrder=3 和 LabelText="Totals”。(我这不是写的,是我继承的)
    • 您的解释是对我所写内容的逻辑补充。我将编辑答案以使其更清楚。
    • 可能有一些我不知道的 Access 怪癖(加上问题中的括号不平衡,所以我不能确定)但是从 SQL 代码 点阅读从看来,您似乎错过了一个案例:当GrpOrder = 3LabelText IS NULL 时,OP 的谓词将评估为FALSE,而您的谓词将评估为NULL。虽然确实在 SQL DML 中效果将是从结果集中删除行,但如果在 SQL DDL(例如 CHECK 约束)或计算列中使用它,则效果将是允许更新成功什么时候应该失败。
    • 噢!我错过了GrpOrder IS NULLLabelText IS NULL 时的明显情况:在SQL DML 中(例如,在常规查询的WHERE 子句中),您的谓词将删除该行,而它将被OP 保留。
    • “我不记得访问是否使用 或 != 来表示不等式”——听起来您没有对此进行测试,因此我想您错过了两种情况。访问使用&lt;&gt;,顺便说一句。
    【解决方案3】:

    我不想要任何记录 GrpOrder=3 除了 GrpOrder=3 和 LabelText="总计"。

    where GrpOrder <> 3 or (GrpOrder = 3 and LabelText="Totals")
    

    【讨论】:

    • 我认为这不能正确处理GrpOrder IS NULL 时的情况。
    • 这是正确的,但是可以删除 GrpOrder = 3 谓词,因为只要它为假,由于 GrpOrder 3,整个子句将始终为真。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 1970-01-01
    • 2014-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多