【问题标题】:How do I join the most recent row in one table to another table?如何将一个表中的最新行连接到另一个表?
【发布时间】:2010-10-04 14:17:16
【问题描述】:

我的数据如下所示:

entities
id         name
1          Apple
2          Orange
3          Banana

会定期运行一个进程并为每个实体打分。该过程生成数据并将其添加到分数表中,如下所示:

scores 
id  entity_id    score   date_added
1    1            10       1/2/09
2    2            10       1/2/09
3    1            15       1/3/09
4    2            10       1/03/09
5    1            15       1/4/09
6    2            15       1/4/09
7    3            22       1/4/09

我希望能够选择所有实体以及每个实体的最新记录分数,从而产生如下数据:

entities
id name     score  date_added
1  Apple     15     1/4/09
2  Orange    15     1/4/09
3  Banana    15     1/4/09

我可以使用此查询获取单个实体的数据:

SELECT entities.*, 
       scores.score, 
       scores.date_added 
FROM entities

INNER  JOIN scores
ON entities.id = scores.entity_id

WHERE entities.id = ?

ORDER BY scores.date_added DESC
LIMIT 1

但我不知道如何为所有实体选择相同的。也许它在盯着我看?

非常感谢您抽出宝贵时间。

感谢您的好评。我会给它几天时间,看看是否有首选的解决方案冒出来,然后我会选择答案。

更新:我已经尝试了几个建议的解决方案,我现在面临的主要问题是,如果一个实体还没有生成的分数,它们就不会出现在列表中。

确保返回所有实体(即使它们还没有发布任何分数)的 SQL 是什么样的?

更新:已选择答案。谢谢大家!

【问题讨论】:

    标签: sql date join greatest-n-per-group


    【解决方案1】:

    我是这样做的:

    SELECT e.*, s1.score, s1.date_added 
    FROM entities e
      INNER JOIN scores s1
        ON (e.id = s1.entity_id)
      LEFT OUTER JOIN scores s2
        ON (e.id = s2.entity_id AND s1.id < s2.id)
    WHERE s2.id IS NULL;
    

    【讨论】:

    • 谢谢比尔,我最终决定采用此解决方案,但将 INNER JOIN 换成 LEFT JOIN 以包含尚未得分的实体。
    • 我喜欢这个解决方案,我也在使用 LEFT JOIN。如果同一实体在同一日期有两个分数,您建议如何处理平局?
    • 我试图在连接中设置一个基于日期字段的查询,以便我可以在最近的记录中获得结果,而不是在具有未来日期的记录上连接。我必须在内连接和左外连接中进行子查询,选择我的日期字段为
    • @BillKarwin,WHERE s2.id IS NULL 的目的是什么?
    • @dev1998,仅当 OUTER JOIN 在 s2 中没有找到符合条件的行时,s2.id 才会为 NULL。也就是说,没有任何行具有相同的 entity_id 且 id 大于 大于 s1.id。如果不存在这样的行,则必须意味着 s1 是该实体的 id 最大的行。
    【解决方案2】:

    只是添加我的变体:

    SELECT e.*, s1.score
    FROM entities e
    INNER JOIN score s1 ON e.id = s1.entity_id
    WHERE NOT EXISTS (
        SELECT 1 FROM score s2 WHERE s2.id > s1.id
    )
    

    【讨论】:

    • 我喜欢这个!至少在 SQL Server 上,这将运行得非常快。现在,我建议将 INNER 更改为 LEFT JOIN,以防万一刚刚添加了新实体并且流程尚未运行。
    • 为了提高速度,您可以将存在性测试作为加入条件的一部分。至少在 SQL S 上。它们是在 WHERE 过滤完成之前执行的,因此您可以通过在此处修剪搜索来节省每行几毫秒。
    • 嗯,WHERE 过滤不一定要在 JOIN 子句之后进行。事实上,它们可以先完成,特别是如果 WHERE 子句过滤 INDEX...
    【解决方案3】:

    接近 1

    SELECT entities.*, 
           scores.score, 
           scores.date_added 
    FROM entities
    
    INNER  JOIN scores
    ON entities.id = scores.entity_id
    
    WHERE scores.date_added = 
      (SELECT max(date_added) FROM scores where entity_id = entities.id)
    

    【讨论】:

    • 如果 [scores] 由 [entity_id] 索引,则性能最佳(到目前为止)
    • 在我的测试中,此解决方案似乎会为在同一(最新)日期为该实体添加多个分数的任何实体返回多行。
    • 它也会跳过没有分数的行。
    【解决方案4】:

    我知道这是一个老问题,只是想添加一种尚未有人提及的方法,Cross ApplyOuter Apply。这些在 SQL Server 2005 中可用(此问题中未标记数据库类型)或更高版本

    使用临时表

    DECLARE @Entities TABLE(Id INT PRIMARY KEY, name NVARCHAR(MAX))
    INSERT INTO @Entities
    VALUES (1, 'Apple'), (2, 'Orange'), (3, 'Banana'), (4, 'Cherry')
    
    DECLARE @Scores TABLE(Id INT PRIMARY KEY, Entity_Id INT, Score INT, Date_Added DATE)
    INSERT INTO @Scores
    VALUES (1,1,10,'2009-02-01'),
    (2,2,10,'2009-02-01'),
    (3,1,15,'2009-02-01'),
    (4,2,10,'2009-03-01'),
    (5,1,15,'2009-04-01'),
    (6,2,15,'2009-04-01'),
    (7,3,22,'2009-04-01')
    

    你可以使用

    SELECT E.Id, E.name, S.Score, S.Date_Added 
    FROM @Entities E
    CROSS APPLY
    (
        SELECT TOP 1 * 
        FROM @Scores Sc 
        WHERE Sc.Entity_Id = E.Id  
        ORDER BY sc.Score DESC
    ) AS S
    

    得到想要的结果。允许没有分数的实体的等价物是

    SELECT E.Id, E.name, S.Score, S.Date_Added 
    FROM @Entities E
    OUTER APPLY
    (
        SELECT TOP 1 * 
        FROM @Scores Sc 
        WHERE Sc.Entity_Id = E.Id  
        ORDER BY sc.Score DESC
    ) AS S
    

    【讨论】:

      【解决方案5】:

      方法 2

      相对于批次的查询成本:


      SELECT entities.*, 
             scores.score, 
             scores.date_added 
      FROM entities
      
      INNER  JOIN scores
      ON entities.id = scores.entity_id
      
      inner join 
          (
          SELECT 
                 entity_id, max(date_added) as recent_date
          FROM scores
          group by entity_id
          ) as y on entities.id = y.entity_id and scores.date_added = y.recent_date
      

      【讨论】:

        【解决方案6】:
        SELECT entities.*, 
               scores.score, 
               scores.date_added 
        FROM entities
        
        INNER  JOIN scores
        ON entities.id = scores.entity_id
        
        WHERE entities.id in 
        (select id from scores s2 where date_added = max(date_added) and s2.id = entities.id)
        
        ORDER BY scores.date_added DESC
        LIMIT 1
        

        【讨论】:

        • 您的子查询使用的列 (date_added) 在您查询的表中不存在。
        【解决方案7】:

        您现在也可以在大多数 RDBMS(Oracle、PostgreSQL、SQL Server)中使用 ROW_NUMBER 等窗口函数进行自然查询:

        SELECT id, name, score, date_added FROM (
         SELECT e.id, e.name, s.score, s.date_added,
         ROW_NUMBER() OVER (PARTITION BY e.id ORDER BY s.date_added DESC) rn
         FROM Entities e INNER JOIN Scores s ON e.id = s.entity_id
        ) tmp WHERE rn = 1;
        

        SQL Fiddle

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-06
          • 1970-01-01
          • 2011-02-22
          • 1970-01-01
          • 2011-12-27
          • 2011-11-01
          • 1970-01-01
          相关资源
          最近更新 更多