【问题标题】:select the most recent entry选择最近的条目
【发布时间】:2017-03-19 00:49:30
【问题描述】:

我有下表:

LOCATION_ID, PERSON_ID, DATE
3, 65, 2016-06-03
7, 23, 2016-10-28
3, 23, 2016-08-05
5, 65, 2016-07-14

我想在 PL/SQL 中构建一个选择查询,以选择每个 person_id 具有最近 location_id 的记录。对于上面的示例,期望的结果应该是:

LOCATION_ID, PERSON_ID, DATE
5, 65, 2016-07-14
7, 23, 2016-10-28

(日期表示为'YYYY-MM-DD')

谢谢!

【问题讨论】:

  • 您检查收到的所有答案了吗?您将效率最低的一项标记为“正确”。 Wernfried 的second 解决方案显然是最好的解决方案。它只遍历整个表 - 没有连接,因此它比任何基于连接的解决方案都要快得多。第二好的解决方案(两次通过,但仍然没有连接,所以仍然快得多)是 Hawk 的解决方案和 Wernfried 的 first 解决方案。如果你不明白他们的解决方案,你可以
  • 我将我阅读并为我工作的第一个答案标记为“正确”。今天,我阅读了所有的答案。

标签: sql oracle greatest-n-per-group


【解决方案1】:

其他建议是正确的,但最紧凑和最快的解决方案最有可能使用FIRST_VALUE and LAST_VALUE Analytic Functions

SELECT DISTINCT
   FIRST_VALUE(LOCATION_ID) OVER (PARTITION BY PERSON_ID ORDER BY THE_DATE 
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS LOCATION_ID, 
   PERSON_ID, 
   MAX(THE_DATE) OVER (PARTITION BY PERSON_ID) AS LAST_DATE
FROM YOUR_TABLE;

其他人更喜欢

SELECT 
   MAX(LOCATION_ID) KEEP (DENSE_RANK FIRST ORDER BY DATE) as LOCATION, 
   PERSON_ID, 
   MAX(DATE) as LAST_DATE
FROM YOUR_TABLE
GROUP BY PERSON_ID;

也一样,但我对这个子句不太熟悉。见aggregate_function KEEP

【讨论】:

  • 选择第二种解决方案是合理的,因为它是正确的。它与第一个不同。第一个解决方案使用分析函数,因此结果将具有与基表一样多的行(具有许多重复项)。您需要再次从中选择(或“分组”或做某事)以删除重复项。
  • @mathguy,我在第一个查询中使用了 DISTINCT。应该是正确的。
  • 哦,对不起,我没注意。从某种意义上说它是“正确的”,它会给出正确的答案,但如果表很大,它可能效率很低。通常,当您必须使用“distinct”(显式或隐藏在 GROUP BY 等中)时,这表明可能存在更好的解决方案。在这种情况下它确实 - 这是你的第二个解决方案。
  • 嗯,第二个也包含了GROUP BY,我认为性能上没有任何区别。也许在按 PERSON_ID 分区的大型表上,GROUP BY 可能更有效,因为 Oracle 可以并行化查询。
  • 在第一个解决方案中,您在 partition by 子句中“分组”person_id,然后您仍然没有完成 - 您仍然必须“区分”结果。但是您可能是对的,除了非常大的表之外,性能上的任何差异都可能很小。我可能会进行一些测试并报告——我发现有时结果令人惊讶(也许第一个解决方案会比第二个更快!)
【解决方案2】:

您可以先按PERSON_ID 对结果进行分组并选择MAX(DATE),从而为每个人提取最近的事件。

然后在这两列上将表与自身连接起来以检索LOCATION_ID

SELECT
  YOUR_TABLE.LOCATION_ID,
  YOUR_TABLE.PERSON_ID,
  YOUR_TABLE.DATE
FROM
  (SELECT
    PERSON_ID, MAX(DATE) AS max_date
  FROM
    YOUR_TABLE
  GROUP BY
    PERSON_ID
  ) AS t1
LEFT JOIN
  YOUR_TABLE
ON
  YOUR_TABLE.PERSON_ID = t1.PERSON_ID
  AND
  YOUR_TABLE.DATE = t1.max_date

顺便说一句,你不应该在列名中使用像DATE 这样的保留字。

这里是显示它工作的小提琴:http://sqlfiddle.com/#!9/efdcb/2

【讨论】:

  • 日期名称只是为了提出我的问题。感谢您指出这一点
【解决方案3】:

@quasoft 是正确的。处理此类GROUP BY 问题的另一种方法(当您想要返回的列多于您想要分组的列时。在您的情况下,您需要返回 location_id、person_id。但您只需要按 person_id 分组),是使用analytical functions

--schema:
CREATE TABLE my_table 
  ( 
     location_id NUMBER, 
     person_id   NUMBER, 
     date_       DATE 
  ); 

INSERT ALL 
INTO my_table 
VALUES (3, 65, To_date('2016-06-03', 'YYYY-MM-DD')) 
INTO my_table 
VALUES (7, 23, To_date('2016-10-28', 'YYYY-MM-DD')) 
INTO my_table 
VALUES (3, 23, To_date('2016-08-05', 'YYYY-MM-DD')) 
INTO my_table 
VALUES (5, 65, To_date('2016-07-14', 'YYYY-MM-DD')) 
SELECT * 
FROM   dual; 

--query:
WITH ordered 
     AS (SELECT location_id, 
                person_id, 
                date_, 
                Row_number() 
                  over ( 
                    PARTITION BY person_id 
                    ORDER BY date_ DESC) RN 
         FROM   my_table) 
SELECT location_id, 
       person_id, 
       date_ 
FROM   ordered 
WHERE  rn = 1; 

查询ordered 按日期对每个组的行进行排序。主查询,在排序后返回每个组的前 1。因此,在这种情况下,它将返回最后一个(我们按 date_desc 排序)。

【讨论】:

    【解决方案4】:

    这可能行得通!

    SELECT * FROM Your_Table A
    JOIN (SELECT PERSON_ID,MAX(DATE) as MaxDate FROM Your_Table 
    GROUP BY PERSON_ID) B
    ON A.PERSON_ID = B.PERSON_ID AND A.DATE = B.MaxDate
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-17
      • 2016-05-08
      • 2020-10-21
      • 1970-01-01
      • 1970-01-01
      • 2016-10-03
      • 2013-08-10
      • 1970-01-01
      相关资源
      最近更新 更多