【问题标题】:Dynamic/Conditional SQL Join?动态/条件 SQL 连接?
【发布时间】:2009-02-19 18:42:58
【问题描述】:

我在 MSSQL 表 (TableB) 中有数据,其中 [dbo].tableB.myColumn 在某个日期后更改格式...

我正在对该表进行简单的联接..

Select [dbo].tableB.theColumnINeed from [dbo].tableA 
left outer join [dbo].tableB on [dbo].tableA.myColumn = [dbo].tableB.myColumn

但是,我需要根据表 A 中的日期列 ([dbo].tableA.myDateColumn) 使用不同的格式加入。

有点像……

Select [dbo].tableB.theColumnINeed from [dbo].tableA 
left outer join [dbo].tableB on [dbo].tableA.myColumn = 
    IF [dbo].tableA.myDateColumn > '1/1/2009'
        BEGIN
            FormatColumnOneWay([dbo].tableB.myColumn)
        END
    ELSE
        BEGIN
            FormatColumnAnotherWay([dbo].tableB.myColumn)
        END

我想知道是否有办法做到这一点.. 或者我没有想到的更好的方法来解决这个问题..

【问题讨论】:

    标签: sql sql-server join


    【解决方案1】:
    SELECT [dbo].tableB.theColumnINeed
    FROM   [dbo].tableA 
    LEFT OUTER JOIN [dbo].tableB
    ON [dbo].tableA.myColumn = 
       CASE
        WHEN [dbo].tableA.myDateColumn <= '1/1/2009' THEN FormatColumnOneWay([dbo].tableB.myColumn)
        ELSE FormatColumnAnotherWay([dbo].tableB.myColumn)
       END
    

    【讨论】:

      【解决方案2】:

      您可以考虑使用 UNION,而不是在 JOIN 中使用 CASE 语句,这会阻止使用索引的查询

      SELECT [dbo].tableB.theColumnINeed 
      FROM   [dbo].tableA 
          LEFT OUTER JOIN [dbo].tableB 
               ON [dbo].tableA.myDateColumn > '1/1/2009'
              AND [dbo].tableA.myColumn = FormatColumnOneWay([dbo].tableB.myColumn)
      UNION ALL
      SELECT [dbo].tableB.theColumnINeed 
      FROM   [dbo].tableA 
          LEFT OUTER JOIN [dbo].tableB 
               ON [dbo].tableA.myDateColumn <= '1/1/2009'
              AND [dbo].tableA.myColumn = FormatColumnAnotherWay([dbo].tableB.myColumn)
      

      但如果 FormatColumnOneWay / FormatColumnAnotherWay 是函数或字段表达式,则可能会排除在 [myColumn] 上使用索引,尽管仍应使用 myDateColumn 上的任何索引

      但是,了解什么是 FormatColumnOneWay / FormatColumnAnotherWay 逻辑可能会有所帮助,众所周知,这可以实现更好的优化

      需要注意的几点:

      UNION ALL 不会删除任何重复项(与 UNION 不同)。因为这两个子查询是互斥的,所以这没关系,并保存了 UNION 将执行的 SORT 步骤以使其能够删除重复项。

      字符串日期不应使用“1/1/2009”样式,应使用不带斜线或连字符的“yyyymmdd”样式(您也可以使用带有参数的 CONVERT 来明确指示字符串在 d /m/y 或 m/d/y 样式

      【讨论】:

        【解决方案3】:

        在 SQL Server 中,您会使用 CASE,例如:

        SELECT * 
        FROM TableA
        INNER JOIN TableB on TableA.Column=
        CASE WHEN TableA.RecordDate>'1/2/08'
               THEN FormatCoumn(TableB.Column) 
             ELSE FormatColumnOtherWat(TableB.Column)
        END
        

        【讨论】:

        • 我的建议是修复数据,因为优化器将忽略 JOIN 条件中具有这些函数的索引
        • 是的,但有时您无法修复数据;-)
        • 这是同一列,我会修复它,在其上放置一个 CHECK CONSTRAINT 以便它不会再次发生,因为迟早有人会尖叫性能不可接受,然后呢?跨度>
        【解决方案4】:

        您知道这对性能不利,因为您将无法正确使用索引?

        你可以使用 CASE 语句 kludge 或者...你可以去修复数据,这样你就可以使用索引,它会快很多倍

        【讨论】:

          【解决方案5】:

          我同意CASE 语法更适合阅读目的,尽管我不知道运行时间是否有任何显着差异。

          真正要做的“正确”事情是重新做它并从头开始做正确的事情。您的日期应该存储在datetime 列中,并且将tableB 中的所有日期迁移到日期时间列可能会有很多收获。您可以这样做:

          1. TableB 添加一个虚拟列,类型为datetime
          2. 运行从当前列获取日期值并将其放入日期时间列的查询。
          3. 重命名和删除列以匹配之前的数据结构。

          【讨论】:

          • 您忘记了第 4 步:花费数周或数月的时间查找因删除列而导致其他代码/报告中的所有错误
          • 嗯,不存储在日期时间列中的日期时间值也是邪恶的。根据使用数据库的应用程序有多大,可能会出现很多问题——是的,但是如果您使用了良好的关注点分离等,您将不会有很多地方需要更改。为什么要花时间破解臭代码?
          • 他确实说这是一个日期列,但他从未真正说过这是一个 varchar/nvarchar/whatever。
          【解决方案6】:

          好的,等一下。列的实际数据类型是什么?我猜它不是 DateTime,因为你并不能真正控制格式……它只是存储一个日期。它可以 CAST 或转换为 DateTime 吗?

          所以你可能想要

          left outer join tableb on tableA.myColumn = CAST(tableb.MyColumn as DateTime)
          

          这样你匹配的不是字符串,而是应该更可靠的实际日期。它也更简单,更易于阅读。真正的问题是为什么日期没有首先存储为 DateTime...

          【讨论】:

            【解决方案7】:

            [dbo] 前缀,我相信您使用的是 SQL Server。虽然我没有太多经验,但您可以将这两个字段都转换为特定的日期格式:

            select * from tableA
              Left Outer join tableB
                   On CONVERT(CHAR(8), tableA.myColumn, 112) = CONVERT(CHAR(8), tableB.myColumn, 112)
            

            同样适用于任何 DBMS,使用适当的日期格式化函数。

            我不了解 SQL Server,但在 Oracle 中,您可以为连接表达式创建索引。

            【讨论】:

              【解决方案8】:

              好吧,您可以在连接之前使用子查询来正确格式化任一表中的数据。

              SELECT
                newB.columnINeed
              FROM
                tableA AS A
              LEFT OUTER JOIN (
                SELECT
                  columnINeed
                , CASE WHEN myColumn > '1/1/2009' THEN FormatColumnOneWay(myColumn)
                  ELSE FormatColumnAnotherWay(myColumn)
                  END AS myColumn
                FROM
                  tableB
              ) AS NewB ON A.myColumn = B.myColumn
              

              如果性能很重要,您可以使用索引视图(基于子查询)而不是将子查询硬编码到整个查询中。

              【讨论】:

              • 您可能无法执行此操作。我注意到您在 A 的基础上格式化 B。我猜您可能可以在不涉及 A 的情况下格式化 B,然后进行连接?
              猜你喜欢
              • 1970-01-01
              • 2018-04-09
              • 2015-11-08
              • 1970-01-01
              • 2013-05-18
              • 2014-12-25
              • 2011-03-29
              相关资源
              最近更新 更多