简体   繁体   English

MySQL: Select 来自 3 个关系表的所有具有 MAX() 值的行

[英]MySQL: Select all row with MAX() value from 3 relational tables

I did try to look for this answer, but no luck: (我确实试图寻找这个答案,但没有运气:(

It should be something basic, but I can't make it work...它应该是基本的东西,但我不能让它工作......

I have 3 tables:我有 3 张桌子:

  1. flightPerson飞行人
personID个人ID flightID航班号
1 1 587 587
51 51 44 44
1 1 37 37
... ... ... ...
  1. flight航班
flightID航班号 departure离开 arrive到达
37 37 1998-06-01 1998-06-01 1998-06-03 1998-06-03
587 587 2022-01-01 2022-01-01 2022-01-02 2022-01-02
44 44 2022-01-01 2022-01-01 0000-00-00 0000-00-00
... ... ... ... ... ...
  1. country国家
personID个人ID countryID国家ID
1 1 12 12
51 51 27 27
... ... ... ...

GOAL: select peopleID, flightID, departure, arrive, countryID 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) )目标:select peopleID,flightID,出发,到达,countryID WHERE countryID = 12 AND(LAST FLIGHT 是在 2000-01-01 之前或 LAST FLIGHT 是(在 2022-03-01 之后并且到达时间是 0000-00-00))

Thanks for your help!谢谢你的帮助!

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

There are 2 general solutions to this, the simplest to explain is to use the ROW_NUMBER() window query to select the Last Flight instead of a group by.对此有两种通用解决方案,最简单的解释是使用ROW_NUMBER() window 查询select最后一次飞行而不是 group by。 In MySQL v8 we can use a CTE to help keep the query readable:在 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

Unfortunately, for your provided dataset, there are no results to this query.不幸的是,对于您提供的数据集,此查询没有结果。 Lets remove the departure filter to understand, in fact, lets move the departure filter to a select column to project it into the output:让我们去掉departure过滤器来理解,其实我们把departure过滤器移到一个select列来投影到output中:

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个人ID flightID航班号 departure离开 arrive到达 countryID国家ID Output Output
1 1 587 587 2022-01-01 2022-01-01 2022-01-02 2022-01-02 12 12

View this proof in a fiddle: https://www.db-fiddle.com/f/jKsg1B5RjW5UhTsLbtQHwe/0 Update the schema there with additional data to see if your desired flights are included.在小提琴中查看此证明: https://www.db-fiddle.com/f/jKsg1B5RjW5UhTsLbtQHwe/0使用其他数据更新那里的架构,以查看是否包含所需的航班。

So the last flight for personID=1 was on 2022-01-02, which is not in the required range.所以personID=1的最后一次飞行是在 2022-01-02,这不在要求的范围内。 personID=51 flights are excluded as their country is 27, but the date of their last flight departure, even though it still has not yet landed;) is not in the filtered range. personID=51航班被排除在外,因为他们的国家是 27,但是他们最后一次航班起飞的日期,即使它还没有降落;)不在过滤范围内。

Legacy Versions旧版本

For older versions, we can't use CTEs or the ROW_NUMBER() window function, so lets go back to using GROUP BY .对于旧版本,我们不能使用 CTE 或 ROW_NUMBER() window function,所以让 go 回到使用GROUP BY

The problem with a GROUP BY , although it seems logical, is that you need to first apply the grouping to determine the Last Flight and then you need to apply your filter only to the results from the Last Flight query. GROUP BY的问题虽然看起来合乎逻辑,但您需要首先应用分组来确定Last Flight ,然后您需要仅将过滤器应用于Last Flight查询的结果。 That is one of the problems that using a CTE also solved for us, in this case we will have to use a nested query:这也是使用 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;

You can see in this query we only get the departure of the Last Flight, which isn't very efficient to join back into the rest of the query, I would prefer to get the ID of the last flight and use that, but to get the ID will require a different type of sub-query that might be even more inefficient, it is certainly increasing in complexity and becoming harder to read :你可以在这个查询中看到我们只得到最后一次航班的departure ,这不是很有效地加入查询的 rest,我更愿意得到最后一次航班的 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 (f.departure < '2000-01-01' OR f.departure > '2022-03-01' AND f.arrive = '0000-00-00')
ORDER BY personID ASC;

View this fiddle in MySQL 5.6 here: http://sqlfiddle.com/#!9/a8e82d/3在 MySQL 5.6 中查看此小提琴: http://sqlfiddle.com/#!9/a8e82d/3

Finally, a special note about the OR clause...最后,关于OR子句的特别说明......

Your original filter expression:您的原始过滤器表达式:

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")

Is missing some brackets, which I think you are aware of as you had the brackets correct in your explanation:缺少一些括号,我认为您知道这些括号,因为您的解释中的括号是正确的:

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

What I like to do (because I am an application developer first) is to use indentation to visually separate the content within brackets to make it easier to identify the sub-expressions.我喜欢做的(因为我首先是一个应用程序开发人员)是使用缩进在视觉上分隔括号内的内容,以便更容易识别子表达式。

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)
  )

But in this case the inner brackets are not necessary because the OR statement will evaluate the expression on either side of the OR independently:但在这种情况下,内括号不是必需的,因为OR语句将独立评估OR两侧的表达式:

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

Which fits on one line, but perhaps is easier to read like this:它适合一行,但可能更容易阅读如下:

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

FYI: The fact that X number of tables is involved here is not really the complicating factor.仅供参考:这里涉及X个表的事实并不是真正的复杂因素。 The problem is that you want to apply additional logic to the grouped results, without that logic affecting the grouping.问题是您希望将其他逻辑应用于分组结果,而该逻辑不会影响分组。 You then also want additional metadata from the rows that correlate to the MAX/MIN or FIRST/LAST record from the grouping.然后,您还需要与分组中的 MAX/MIN 或 FIRST/LAST 记录相关的行中的其他元数据。

In SQL that can really only be achieved though sub-queries, whether you use CTEs or Views or Table Valued Variables, the execution is the same, we need to force the engine to evaluate one resultset before applying additional filtering criteria.在 SQL 中,实际上只能通过子查询来实现,无论您使用 CTE 还是视图或表值变量,执行都是相同的,我们需要强制引擎在应用附加过滤条件之前评估一个结果集。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM