有两种通用解决方案,最简单的解释是使用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 还是视图或表值变量,执行都是一样的,我们需要强制引擎在应用附加过滤条件之前评估一个结果集。