【问题标题】:How do I join to another table and return only the most recent matching row?如何加入另一个表并仅返回最近的匹配行?
【发布时间】:2016-07-21 01:00:44
【问题描述】:

我有一个存储合同行的表格。每个合同行都有自己的唯一 ID,它也有其父合同的 ID。示例:

+-------------+---------+
| contract_id | line_id |
+-------------+---------+
|        1111 |     100 |
|        1111 |     101 |
|        1111 |     102 |
+-------------+---------+

我有另一个表来存储合同行的历史更改。例如,每次更改合同行上的单位数时,都会在表中添加一个新行。示例:

+-------------+---------+--------------+-------+
| contract_id | line_id | date_changed | units |
+-------------+---------+--------------+-------+
|        1111 |     100 | 2016-01-01   |     1 |
|        1111 |     100 | 2016-02-01   |     2 |
|        1111 |     100 | 2016-03-01   |     3 |
+-------------+---------+--------------+-------+

如您所见,属于 ID 为 1111 的合同的 ID 为 100 的合同行已在 3 个月内被编辑了 3 次。当前值为 3 个单位。

我正在对合同行表运行查询以选择所有数据。我想加入历史数据表并为每个合同行选择最近的行并在我的结果中显示单位。我该怎么做?

预期结果(101 和 102 也会有单一结果):

+-------------+---------+-------+
| contract_id | line_id | units |
+-------------+---------+-------+
|        1111 |     100 |     3 |
+-------------+---------+-------+

我用左连接尝试了下面的查询,但它返回 3 行而不是 1 行。

查询:

SELECT *, T1.units
FROM contract_lines
LEFT JOIN (
    SELECT contract_id, line_id, units, MAX(date_changed) AS maxdate
    FROM contract_history
    GROUP BY contract_id, line_id, units) AS T1
    ON contract_lines.contract_id = T1.contract_id 
    AND contract_lines.line_id = T1.line_id

实际结果:

+-------------+---------+-------+
| contract_id | line_id | units |
+-------------+---------+-------+
|        1111 |     100 |     1 |
|        1111 |     100 |     2 |
|        1111 |     100 |     3 |
+-------------+---------+-------+

【问题讨论】:

    标签: sql sql-server sql-server-2012


    【解决方案1】:

    额外加入 contract_history 以及 maxdate 将起作用

    SELECT contract_lines.*,T2.units
    FROM contract_lines
    LEFT JOIN (
        SELECT contract_id, line_id, MAX(date_changed) AS maxdate
        FROM contract_history
        GROUP BY contract_id, line_id) AS T1 
        JOIN contract_history T2 ON 
             T1.contract_id=T2.contract_id and 
             T1.line_id= T2.line_id and 
             T1.maxdate=T2.date_changed
    ON contract_lines.contract_id = T1.contract_id
    AND contract_lines.line_id = T1.line_id
    

    输出

    【讨论】:

    • 不同之处在于这里没有units字段。你从原始表中得到的
    • 这对我不起作用。我在最后两行收到一个错误,上面写着“无法绑定多部分标识符'contract_lines.contract_id'”,并且分别与“line_id”相同。这不是我熟悉的错误。有什么想法吗?
    【解决方案2】:

    这是我的首选风格,因为它不需要自我加入,并且清楚地表达了您的意图。此外,它在性能方面与ROW_NUMBER() 方法的竞争非常好。

    select a.*
         , b.units
    from contract_lines as a
    join (
        select a.contract_id
             , a.line_id
             , a.units
             , Max(a.date_changed) over(partition by a.contract_id, a.line_id) as max_date_changed
        from contract_history as a
    ) as b
        on a.contract_id = b.contract_id
       and a.line_id = b.line_id
       and b.date_changed = b.max_date_changed;
    

    【讨论】:

      【解决方案3】:

      另一个可能的解决方案。这使用RANK 对其进行排序/过滤。和你做的一样,只是手法不同。

      SELECT contract_lines.*, T1.units
      FROM contract_lines
      LEFT JOIN (
          SELECT contract_id, line_id, units,
          RANK() OVER (PARTITION BY contract_id, line_id ORDER BY date_changed DESC) AS [rank]
          FROM contract_history) AS T1
      ON contract_lines.contract_id = T1.contract_id 
      AND contract_lines.line_id = T1.line_id
      AND T1.rank = 1
      WHERE T1.units IS NOT NULL
      

      如果您希望数据始终存在,您可以将其更改为 INNER JOIN 并删除 WHERE 子句中的 IS NOT NULL

      很高兴你知道了!

      【讨论】:

        【解决方案4】:

        试试这个简单的查询:

        SELECT TOP 1 T1.*
        FROM contract_lines T0 
            INNER JOIN contract_history T1 
                ON T0.contract_id = T1.contract_id and 
                    T0.line_id = T1.line_id 
        ORDER BY date_changed DESC
        

        【讨论】:

          【解决方案5】:

          在花了一个小时查看它并在 StackOverflow 上大喊有一个罕见的维护期之后似乎总是这样,我在发布问题后不久就解决了我自己的问题。

          为了帮助其他陷入困境的人,我将展示我的发现。这可能不是实现这一目标的有效方法,所以如果有人有更好的建议,我会全力以赴。

          我从这里改编了答案:T-SQL Subquery Max(Date) and Joins

          SELECT *,
                 Units = (SELECT TOP 1 units
                          FROM contract_history
                          WHERE contract_lines.contract_id = contract_history.contract_id
                          AND contract_lines.line_id = contract_history.line_id
                          ORDER BY date_changed DESC
                          )
          FROM ....
          

          【讨论】:

          • 尝试inquisitive_mind 方法,最好执行一个子查询(单表扫描),而不是为每一行选择一次。
          • 嗯,是的,但不需要反对,我的回答没有错,只是效率低下。我什至在答案中说明了这一点。不过还是谢谢...
          • 我试图提供帮助。我没有投反对票。只是忽略巨魔;)
          • @Equalsk 请注意您的语言并在 SO 上保持专业。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-31
          • 2020-06-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多