【问题标题】:MySQL: Select all row with MAX() value from n relational tablesMySQL:从 n 个关系表中选择所有具有 MAX() 值的行
【发布时间】:2022-08-14 14:21:30
【问题描述】:

我确实试图寻找这个答案,但没有运气:(

它应该是一些基本的东西,但我不能让它工作......

我有 3 张桌子:

  1. 飞行人
    personID flightID
    1 587
    51 44
    1 37
    ... ...
    1. 飞行
    flightID departure arrive
    37 1998-06-01 1998-06-03
    587 2022-01-01 2022-01-02
    44 2022-01-01 0000-00-00
    ... ... ...
    1. 国家
    personID countryID
    1 12
    51 27
    ... ...

    GOAL:选择 peopleID、flightID、离开、到达、countryID 其中 countryID = 12 并且(LAST FLIGHT 是在 2000-01-01 之前或 LAST FLIGHT 是(在 2022-03-01 之后并且到达时间是 0000-00-00))

    谢谢你的帮助!

    SELECT t1.personID, flight.flightID, MAX(flight.departure), flight.arrive, country.countryID
                                                  FROM flightPerson as t1
                                                  LEFT JOIN flight 
                                                  ON t1.flightID = flight.flightID 
                                                  LEFT JOIN country
                                                  ON country.personID = t1.personID 
                                                  WHERE country.countryID = 12 AND
                                                  flight.departure < \" 2000-01-01 \" OR (flight.departure > \" 2022-03-01\" AND flight.arrive= \"0000-00-00 00:00:00\")  
    GROUP BY personID
    ORDER BY t1.personID  ASC
    

    编辑

    根据 Chris Schaller 的回答,我重建了我的查询,它对我有用。

    基本上(正如 Chris 在第二个 Legacy 查询中建议的那样),您首先需要找到最后一个航班 ID,然后检索该航班的详细信息,然后“获取”(使用 WHERE 子句)只有其出发和到达符合您的条件的航班。

    我把它贴在这里,以防它对遇到同样问题的其他人有用; )

    SELECT personID 
                            FROM (
                                SELECT DISTINCT t2.personID, country.countryID
                                    ,(SELECT t1.flightID
                                        FROM flightPerson
                                        LEFT JOIN flight as t1
                                          ON t1.flightID = flightPerson.flightID 
                                        WHERE t2.personID = flightPerson.personID
                                        ORDER BY `t1`.` departure` DESC
                                        LIMIT 1
                                    )AS lastFlightID
                                FROM flightPerson as t2
                                LEFT JOIN country 
                                ON country.personID = t2.personID
                            ) AS details
                            LEFT JOIN flight AS b
                            ON details. lastFlightID = b.flightID
                            WHERE details.countryID = 12 
                                AND ( b.departure < \"2000-01-01\"  
                                    OR 
                                    (b.departure > \"2022-03-01\" AND b.arrive = \"0000-00-00 00:00:00\") 
                                    )
    

    PS对不起克里斯,我仍然需要使用双括号 AND (... OR (...) )

    这种方式让我感觉更安全......:D

  • 你用的是什么版本的 MySQL?
  • 在以后的帖子中,请包括所提供示例数据的预期输出。是的,我们希望您手动构建此表,我们认识到,如果您可以查询它,您一开始就不会发布;)但实际数据有助于我们识别和克服拼写错误或难以简单解释的逻辑英文术语。

标签: mysql


【解决方案1】:

有两种通用解决方案,最简单的解释是使用ROW_NUMBER()窗口查询选择最后一次飞行而不是 group by。在 MySQL v8 中,我们可以使用 CTE 来帮助保持查询的可读性:

WITH PersonFlightData as (
  SELECT t1.personID, flight.flightID, flight.departure, flight.arrive, country.countryID
  , ROW_NUMBER() OVER(PARTITION BY t1.personID ORDER BY flight.departure DESC) as RN
  FROM flightPerson as t1
  LEFT JOIN flight ON t1.flightID = flight.flightID 
  LEFT JOIN country ON country.personID = t1.personID 
  WHERE country.countryID = 12 
)
SELECT personID, flightID, departure, arrive, countryID
FROM PersonFlightData
WHERE RN = 1 --(filters to only include the LAST flight for each person)
  AND (departure < '2000-01-01' OR departure > '2022-03-01' AND arrive = '0000-00-00')
ORDER BY personID ASC

不幸的是,对于您提供的数据集,此查询没有结果。让我们删除departure 过滤器来理解,实际上,让我们将departure 过滤器移动到选择列以将其投影到输出中:

WITH PersonFlightData as (
  SELECT t1.personID, flight.flightID, flight.departure, flight.arrive, country.countryID
  , ROW_NUMBER() OVER(PARTITION BY t1.personID ORDER BY flight.departure DESC) as RN
  FROM flightPerson as t1
  LEFT JOIN flight ON t1.flightID = flight.flightID 
  LEFT JOIN country ON country.personID = t1.personID 
  WHERE country.countryID = 12 
)
SELECT personID, flightID, departure, arrive, countryID
  , CASE WHEN departure < '2000-01-01' OR departure > '2022-03-01' AND arrive = '0000-00-00' THEN 1 END as Output
FROM PersonFlightData
WHERE RN = 1 --(filters to only include the LAST flight for each person);
personID flightID departure arrive countryID Output
1 587 2022-01-01 2022-01-02 12

在小提琴中查看此证明:https://www.db-fiddle.com/f/jKsg1B5RjW5UhTsLbtQHwe/0 使用其他数据更新那里的架构,以查看是否包含您想要的航班。

所以personID=1 的最后一次飞行是在 2022 年 1 月 2 日,这不在要求的范围内。 personID=51 航班被排除在外,因为他们的国家是 27,但他们最后一次航班起飞的日期,即使它还没有降落;) 不在过滤范围内。

旧版本

对于旧版本,我们不能使用 CTE 或 ROW_NUMBER() 窗口函数,所以让我们回到使用 GROUP BY

GROUP BY 的问题虽然看起来合乎逻辑,但您需要首先应用分组来确定最后一次飞行然后您只需要将过滤器应用于最后一次飞行询问。这也是使用 CTE 为我们解决的问题之一,在这种情况下,我们将不得不使用嵌套查询:

SELECT d.personID, f.flightID, f.departure, f.arrive, countryID
FROM (
  SELECT t1.personID, MAX(flight.departure) AS LastFlightDeparture
  FROM flightPerson as t1
  LEFT JOIN flight ON t1.flightID = flight.flightID 
  GROUP BY personID
) d
LEFT JOIN flightPerson fp ON d.personID = fp.personID
LEFT JOIN flight f ON fp.flightID = f.flightID AND f.departure = d.LastFlightDeparture
LEFT JOIN country ON country.personID = d.personID 
WHERE country.countryID = 12 
  AND (f.departure < '2000-01-01' OR f.departure > '2022-03-01' AND f.arrive = '0000-00-00')
ORDER BY personID ASC;

您可以在此查询中看到,我们仅获得最后一次飞行的 departure,这对于加入查询的其余部分不是很有效,我更愿意获取最后一次飞行的 ID 并使用它,但是要获得 ID 将需要不同类型的子查询,这可能会更加低效,它的复杂性肯定在增加,并且变得更难阅读

SELECT personID, flightID, departure, arrive, countryID, LastFlightID
FROM (
  SELECT fp.personID, fp.flightID, f.departure, f.arrive, country.countryID
    , (SELECT flight.flightID
       FROM flight
       LEFT JOIN flightPerson ON flight.flightID = flightPerson.flightID
       WHERE flightPerson.personID = fp.personID
       ORDER BY departure DESC
       LIMIT 1
    ) as LastFlightID
  FROM flightPerson fp
  LEFT JOIN flight f ON fp.flightID = f.flightID
  LEFT JOIN country ON country.personID = fp.personID 
) flightData
WHERE countryID = 12 
  AND flightID = LastFlightID
  AND (departure < '2000-01-01' OR departure > '2022-03-01' AND arrive = '0000-00-00')
ORDER BY personID ASC;

在 MySQL 5.6 中查看这个小提琴:http://sqlfiddle.com/#!9/a8e82d/3

最后,关于OR 子句的特别说明...

您的原始过滤器表达式:

WHERE country.countryID = 12 
  AND flight.departure < " 2000-01-01 " 
   OR (flight.departure > " 2022-03-01" AND flight.arrive= "0000-00-00 00:00:00")

缺少一些括号,我认为您知道这些括号,因为您的解释中的括号是正确的:

其中 countryID = 12 并且(LAST FLIGHT 是在 2000-01-01 之前或 LAST FLIGHT 是(在 2022-03-01 之后并且到达时间是 0000-00-00))

我喜欢做的(因为我首先是应用程序开发人员)是使用缩进将括号内的内容在视觉上分开,以便更容易识别子表达式。

WHERE countryID = 12 
  AND (
        LAST FLIGHT was before 2000-01-01 
        OR LAST FLIGHT was (after 2022-03-01 AND arrive time is 0000-00-00)
  )

但在这种情况下,内括号不是必需的,因为OR 语句将独立评估OR 两侧的表达式:

WHERE country.countryID = 12 
  AND (flight.departure < '2000-01-01' OR flight.departure > '2022-03-01' AND flight.arrive= '0000-00-00')

它适合一行,但可能更容易阅读如下:

WHERE country.countryID = 12 
  AND (
       flight.departure < '2000-01-01' 
       OR flight.departure > '2022-03-01' AND flight.arrive= '0000-00-00'
  )

供参考:事实是X这里涉及的表数量并不是真正的复杂因素。问题是您希望将其他逻辑应用于分组结果,而该逻辑不会影响分组。然后,您还需要与分组中的 MAX/MIN 或 FIRST/LAST 记录相关的行中的其他元数据。

在真正只能通过子查询实现的 SQL 中,无论您使用 CTE 还是视图或表值变量,执行都是一样的,我们需要强制引擎在应用附加过滤条件之前评估一个结果集。

【讨论】:

  • 首先感谢您花时间准备如此详细的答案!这是非常非常有帮助的!
  • (...继续)由于我的 MySQL 版本,我无法使用 CTE,因此我不得不深入研究您提出的第二个 Legacy 选项)。不幸的是,当我使用时,我得到 f.departure is not a valid table...因此,根据您的模型,我确实尝试重建我的查询,首先查找 LAST FLIGHT ID,然后附加所有详细信息(出发,到达)最后过滤 WHERE 出发和到达尊重我的条件...我将您的答案标记为选定答案并编辑了我的帖子:)再次感谢!
猜你喜欢
  • 2020-12-07
  • 1970-01-01
  • 2021-10-06
  • 2017-10-17
  • 2018-06-06
  • 2021-12-30
  • 2022-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多