我有一张球员表现表:
CREATE TABLE TopTen (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
home INT UNSIGNED NOT NULL,
`datetime`DATETIME NOT NULL,
player VARCHAR(6) NOT NULL,
resource INT NOT NULL
);
什么查询将返回每个不同的 home
的行,其最大值为 datetime
?换句话说,如何按最大 datetime
(按 home
分组)进行过滤,并在结果中仍然包含其他未分组的非聚合列(例如 player
)?
对于此示例数据:
INSERT INTO TopTen
(id, home, `datetime`, player, resource)
VALUES
(1, 10, '04/03/2009', 'john', 399),
(2, 11, '04/03/2009', 'juliet', 244),
(5, 12, '04/03/2009', 'borat', 555),
(3, 10, '03/03/2009', 'john', 300),
(4, 11, '03/03/2009', 'juliet', 200),
(6, 12, '03/03/2009', 'borat', 500),
(7, 13, '24/12/2008', 'borat', 600),
(8, 13, '01/01/2009', 'borat', 700)
;
结果应该是:
id home datetime player 资源 1 10 04/03/2009 约翰 399 2 11 04/03/2009 juliet 244 5 12 04/03/2009 borat 555 8 13 01/01/2009 borat 700
我尝试了一个子查询来获取每个 home
的最大值 datetime
:
-- 1 ..by the MySQL manual:
SELECT DISTINCT
home,
id,
datetime AS dt,
player,
resource
FROM TopTen t1
WHERE `datetime` = (SELECT
MAX(t2.datetime)
FROM TopTen t2
GROUP BY home)
GROUP BY `datetime`
ORDER BY `datetime` DESC
结果集有 130 行,尽管数据库有 187 行,这表明结果包含一些重复的 home
。
然后我尝试加入一个子查询,该子查询为每行 id
获取最大值 datetime
:
-- 2 ..join
SELECT
s1.id,
s1.home,
s1.datetime,
s1.player,
s1.resource
FROM TopTen s1
JOIN (SELECT
id,
MAX(`datetime`) AS dt
FROM TopTen
GROUP BY id) AS s2
ON s1.id = s2.id
ORDER BY `datetime`
没有。给出所有记录。
我尝试了各种奇特的查询,每一个都有不同的结果,但没有什么能让我更接近解决这个问题。
你是如此接近!您需要做的就是同时选择住宅及其最大日期时间,然后在 BOTH 字段中加入 topten
表:
SELECT tt.*
FROM topten tt
INNER JOIN
(SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt
ON tt.home = groupedtt.home
AND tt.datetime = groupedtt.MaxDateTime
最快的 MySQL
解决方案,没有内部查询且没有 GROUP BY
:
SELECT m.* -- get the row that contains the max value
FROM topten m -- "m" from "max"
LEFT JOIN topten b -- "b" from "bigger"
ON m.home = b.home -- match "max" row with "bigger" row by `home`
AND m.datetime < b.datetime -- want "bigger" than "max"
WHERE b.datetime IS NULL -- keep only if there is no bigger than max
解释:
使用 home
列将表与自身连接起来。使用 LEFT JOIN
可确保表 m
中的所有行都出现在结果集中。那些在表 b
中没有匹配项的将有 b
列的 NULL
。
JOIN
上的另一个条件要求仅匹配来自 b
的行,这些行在 datetime
列上的值大于来自 m
的行。
使用问题中发布的数据,LEFT JOIN
将生成以下对:
+------------------------------------------+--------------------------------+
| the row from `m` | the matching row from `b` |
|------------------------------------------|--------------------------------|
| id home datetime player resource | id home datetime ... |
|----|-----|------------|--------|---------|------|------|------------|-----|
| 1 | 10 | 04/03/2009 | john | 399 | NULL | NULL | NULL | ... | *
| 2 | 11 | 04/03/2009 | juliet | 244 | NULL | NULL | NULL | ... | *
| 5 | 12 | 04/03/2009 | borat | 555 | NULL | NULL | NULL | ... | *
| 3 | 10 | 03/03/2009 | john | 300 | 1 | 10 | 04/03/2009 | ... |
| 4 | 11 | 03/03/2009 | juliet | 200 | 2 | 11 | 04/03/2009 | ... |
| 6 | 12 | 03/03/2009 | borat | 500 | 5 | 12 | 04/03/2009 | ... |
| 7 | 13 | 24/12/2008 | borat | 600 | 8 | 13 | 01/01/2009 | ... |
| 8 | 13 | 01/01/2009 | borat | 700 | NULL | NULL | NULL | ... | *
+------------------------------------------+--------------------------------+
最后,WHERE
子句只保留 b
列中具有 NULL
的对(它们在上表中用 *
标记);这意味着,由于 JOIN
子句的第二个条件,从 m
中选择的行在 datetime
列中具有最大值。
阅读 SQL Antipatterns: Avoiding the Pitfalls of Database Programming 书以了解其他 SQL 技巧。
SQLite
,当匹配列(即“home”)上没有索引时,第一个比 La Voie 的版本慢得多。 (用 24k 行测试得到 13k 行)
home
和 datetime
并且 datetime
是该特定 home
的最大值,会发生什么?
home
和 datetime
的索引。作为一般规则,如果索引包含在 ON
、WHERE
或 ORDER BY
子句中使用的列,则索引会有所帮助。但是,这取决于列的使用方式。如果在表达式中使用列,则索引是无用的。将 EXPLAIN
放在对 find out what indexes are used (and how) 的查询前面。
这里是 T-SQL 版本:
-- Test data
DECLARE @TestTable TABLE (id INT, home INT, date DATETIME,
player VARCHAR(20), resource INT)
INSERT INTO @TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700
-- Answer
SELECT id, home, date, player, resource
FROM (SELECT id, home, date, player, resource,
RANK() OVER (PARTITION BY home ORDER BY date DESC) N
FROM @TestTable
)M WHERE N = 1
-- and if you really want only home with max date
SELECT T.id, T.home, T.date, T.player, T.resource
FROM @TestTable T
INNER JOIN
( SELECT TI.id, TI.home, TI.date,
RANK() OVER (PARTITION BY TI.home ORDER BY TI.date) N
FROM @TestTable TI
WHERE TI.date IN (SELECT MAX(TM.date) FROM @TestTable TM)
)TJ ON TJ.N = 1 AND T.id = TJ.id
EDIT
不幸的是,MySQL 中没有 RANK() OVER 函数。
但可以模拟,参见Emulating Analytic (AKA Ranking) Functions with MySQL。
所以这是 MySQL强>版本:
SELECT id, home, date, player, resource
FROM TestTable AS t1
WHERE
(SELECT COUNT(*)
FROM TestTable AS t2
WHERE t2.home = t1.home AND t2.date > t1.date
) = 0
即使每个 home
有两行或多行且具有相等的 DATETIME
,这也将起作用:
SELECT id, home, datetime, player, resource
FROM (
SELECT (
SELECT id
FROM topten ti
WHERE ti.home = t1.home
ORDER BY
ti.datetime DESC
LIMIT 1
) lid
FROM (
SELECT DISTINCT home
FROM topten
) t1
) ro, topten t2
WHERE t2.id = ro.lid
WHERE ti.home = t1.home
- 你能解释一下语法吗?
WHERE ti.home = t1.home
的 select
查询不需要定义 t1
的 FROM
子句。那么它是如何使用的呢?
我认为这会给你想要的结果:
SELECT home, MAX(datetime)
FROM my_table
GROUP BY home
但是如果您还需要其他列,只需与原始表进行联接(检查 Michael La Voie
答案)
此致。
由于人们似乎不断地遇到这个线程(评论日期范围从 1.5 年开始)并没有这么简单:
SELECT * FROM (SELECT * FROM topten ORDER BY datetime DESC) tmp GROUP BY home
不需要聚合函数...
干杯。
你也可以试试这个,对于大表的查询性能会更好。当每个家庭的记录不超过两个并且它们的日期不同时,它就会起作用。更好的通用 MySQL 查询来自上面的 Michael La Voie。
SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
FROM t_scores_1 t1
INNER JOIN t_scores_1 t2
ON t1.home = t2.home
WHERE t1.date > t2.date
或者如果是 Postgres 或那些提供分析功能的数据库,请尝试
SELECT t.* FROM
(SELECT t1.id, t1.home, t1.date, t1.player, t1.resource
, row_number() over (partition by t1.home order by t1.date desc) rw
FROM topten t1
INNER JOIN topten t2
ON t1.home = t2.home
WHERE t1.date > t2.date
) t
WHERE t.rw = 1
SQLite
,当匹配列(即“home”)上没有索引时,第一个比 La Voie 的版本慢得多。
SELECT tt.*
FROM TestTable tt
INNER JOIN
(
SELECT coord, MAX(datetime) AS MaxDateTime
FROM rapsa
GROUP BY
krd
) groupedtt
ON tt.coord = groupedtt.coord
AND tt.datetime = groupedtt.MaxDateTime
这适用于甲骨文:
with table_max as(
select id
, home
, datetime
, player
, resource
, max(home) over (partition by home) maxhome
from table
)
select id
, home
, datetime
, player
, resource
from table_max
where home = maxhome
在 SQL Server 上试试这个:
WITH cte AS (
SELECT home, MAX(year) AS year FROM Table1 GROUP BY home
)
SELECT * FROM Table1 a INNER JOIN cte ON a.home = cte.home AND a.year = cte.year
这是 MySQL 版本,它只打印一个条目,其中一组中有重复的 MAX(datetime)。
你可以在这里测试http://www.sqlfiddle.com/#!2/0a4ae/1
样本数据
mysql> SELECT * from topten;
+------+------+---------------------+--------+----------+
| id | home | datetime | player | resource |
+------+------+---------------------+--------+----------+
| 1 | 10 | 2009-04-03 00:00:00 | john | 399 |
| 2 | 11 | 2009-04-03 00:00:00 | juliet | 244 |
| 3 | 10 | 2009-03-03 00:00:00 | john | 300 |
| 4 | 11 | 2009-03-03 00:00:00 | juliet | 200 |
| 5 | 12 | 2009-04-03 00:00:00 | borat | 555 |
| 6 | 12 | 2009-03-03 00:00:00 | borat | 500 |
| 7 | 13 | 2008-12-24 00:00:00 | borat | 600 |
| 8 | 13 | 2009-01-01 00:00:00 | borat | 700 |
| 9 | 10 | 2009-04-03 00:00:00 | borat | 700 |
| 10 | 11 | 2009-04-03 00:00:00 | borat | 700 |
| 12 | 12 | 2009-04-03 00:00:00 | borat | 700 |
+------+------+---------------------+--------+----------+
带有用户变量的 MySQL 版本
SELECT *
FROM (
SELECT ord.*,
IF (@prev_home = ord.home, 0, 1) AS is_first_appear,
@prev_home := ord.home
FROM (
SELECT t1.id, t1.home, t1.player, t1.resource
FROM topten t1
INNER JOIN (
SELECT home, MAX(datetime) AS mx_dt
FROM topten
GROUP BY home
) x ON t1.home = x.home AND t1.datetime = x.mx_dt
ORDER BY home
) ord, (SELECT @prev_home := 0, @seq := 0) init
) y
WHERE is_first_appear = 1;
+------+------+--------+----------+-----------------+------------------------+
| id | home | player | resource | is_first_appear | @prev_home := ord.home |
+------+------+--------+----------+-----------------+------------------------+
| 9 | 10 | borat | 700 | 1 | 10 |
| 10 | 11 | borat | 700 | 1 | 11 |
| 12 | 12 | borat | 700 | 1 | 12 |
| 8 | 13 | borat | 700 | 1 | 13 |
+------+------+--------+----------+-----------------+------------------------+
4 rows in set (0.00 sec)
已接受答案的输出
SELECT tt.*
FROM topten tt
INNER JOIN
(
SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home
) groupedtt ON tt.home = groupedtt.home AND tt.datetime = groupedtt.MaxDateTime
+------+------+---------------------+--------+----------+
| id | home | datetime | player | resource |
+------+------+---------------------+--------+----------+
| 1 | 10 | 2009-04-03 00:00:00 | john | 399 |
| 2 | 11 | 2009-04-03 00:00:00 | juliet | 244 |
| 5 | 12 | 2009-04-03 00:00:00 | borat | 555 |
| 8 | 13 | 2009-01-01 00:00:00 | borat | 700 |
| 9 | 10 | 2009-04-03 00:00:00 | borat | 700 |
| 10 | 11 | 2009-04-03 00:00:00 | borat | 700 |
| 12 | 12 | 2009-04-03 00:00:00 | borat | 700 |
+------+------+---------------------+--------+----------+
7 rows in set (0.00 sec)
SELECT c1, c2, c3, c4, c5 FROM table1 WHERE c3 = (select max(c3) from table)
SELECT * FROM table1 WHERE c3 = (select max(c3) from table1)
另一种使用子查询 gt 每组最近行的方法,该子查询基本上计算每组每行的排名,然后过滤掉最近的行,如 rank = 1
select a.*
from topten a
where (
select count(*)
from topten b
where a.home = b.home
and a.`datetime` < b.`datetime`
) +1 = 1
这是每行的排名第 visual demo,以便更好地理解
通过阅读一些评论,如果有两行具有相同的 'home' 和 'datetime' 字段值怎么办?
上述查询将失败,并将针对上述情况返回超过 1 行。为了掩盖这种情况,将需要另一个标准/参数/列来决定在上述情况下应该采用哪一行。通过查看示例数据集,我假设有一个主键列 id
应设置为自动递增。所以我们可以使用这个列来选择最近的行,在 CASE
语句的帮助下调整相同的查询,比如
select a.*
from topten a
where (
select count(*)
from topten b
where a.home = b.home
and case
when a.`datetime` = b.`datetime`
then a.id < b.id
else a.`datetime` < b.`datetime`
end
) + 1 = 1
上面的查询将在相同的 datetime
值中选择具有最高 id 的行
visual demo 表示每一行的排名
为什么不使用: SELECT home, MAX(datetime) AS MaxDateTime,player,resource FROM topten GROUP BY home 我错过了什么吗?
before
中返回 expected
输出,我非常怀疑它在 MySQL 5.7 和 after
中的行为是否不同。
在 MySQL 8.0 中,这可以通过使用带有公共表表达式的 row_number() 窗口函数来有效地实现。
(这里的 row_number() 基本上为每个玩家从资源的降序顺序为 1 开始为每一行生成唯一的序列。因此,对于每个序列号为 1 的玩家行将具有最高的资源值。现在我们需要做的就是选择行每个玩家的序列号为 1。可以通过围绕该查询编写外部查询来完成。但我们使用公共表表达式代替,因为它更具可读性。)
架构:
create TABLE TestTable(id INT, home INT, date DATETIME,
player VARCHAR(20), resource INT);
INSERT INTO TestTable
SELECT 1, 10, '2009-03-04', 'john', 399 UNION
SELECT 2, 11, '2009-03-04', 'juliet', 244 UNION
SELECT 5, 12, '2009-03-04', 'borat', 555 UNION
SELECT 3, 10, '2009-03-03', 'john', 300 UNION
SELECT 4, 11, '2009-03-03', 'juliet', 200 UNION
SELECT 6, 12, '2009-03-03', 'borat', 500 UNION
SELECT 7, 13, '2008-12-24', 'borat', 600 UNION
SELECT 8, 13, '2009-01-01', 'borat', 700
询问:
with cte as
(
select id, home, date , player, resource,
Row_Number()Over(Partition by home order by date desc) rownumber from TestTable
)
select id, home, date , player, resource from cte where rownumber=1
输出:
id home date player 资源 1 10 2009-03-04 00:00:00 约翰 399 2 11 2009-03-04 00:00:00 juliet 244 5 12 2009-03-04 00:00:00 borat 555 8 13 2009 -01-01 00:00:00 波拉特 700
db<>小提琴here
@Michae 接受的答案在大多数情况下都可以正常工作,但对于以下情况却失败了。
如果有 2 行 HomeID 和 Datetime 相同,则查询将返回两行,而不是根据需要返回不同的 HomeID,以便在查询中添加 Distinct,如下所示。
SELECT DISTINCT tt.home , tt.MaxDateTime
FROM topten tt
INNER JOIN
(SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt
ON tt.home = groupedtt.home
AND tt.datetime = groupedtt.MaxDateTime
尝试这个
select * from mytable a join
(select home, max(datetime) datetime
from mytable
group by home) b
on a.home = b.home and a.datetime = b.datetime
问候 K
max(datetime)
的别名是 datetime
。不会有什么问题吗?
datetime
是如何选择的?
这是您需要的查询:
SELECT b.id, a.home,b.[datetime],b.player,a.resource FROM
(SELECT home,MAX(resource) AS resource FROM tbl_1 GROUP BY home) AS a
LEFT JOIN
(SELECT id,home,[datetime],player,resource FROM tbl_1) AS b
ON a.resource = b.resource WHERE a.home =b.home;
希望下面的查询将给出所需的输出:
Select id, home,datetime,player,resource, row_number() over (Partition by home ORDER by datetime desc) as rownum from tablename where rownum=1
(注意:Michael 的答案非常适合目标列 datetime
对于每个不同的 home
不能有重复值的情况。)
如果您的表有 home
xdatetime
的重复行,并且您只需为每个不同的 home
列选择一行,我的解决方案如下:
您的表需要一个唯一的列(例如 id
)。如果没有,请创建一个视图并向其中添加一个随机列。
使用此查询为每个唯一的 home
值选择一行。在重复 datetime
的情况下选择最低的 id
。
SELECT tt.*
FROM topten tt
INNER JOIN
(
SELECT min(id) as min_id, home from topten tt2
INNER JOIN
(
SELECT home, MAX(datetime) AS MaxDateTime
FROM topten
GROUP BY home) groupedtt2
ON tt2.home = groupedtt2.home
) as groupedtt
ON tt.id = groupedtt.id
如果有 2 条具有相同日期和家庭的记录,则接受的答案对我不起作用。加入后将返回 2 条记录。虽然我需要选择其中的任何一个(随机)。此查询用作连接子查询,因此无法仅限制 1。这是我达到预期结果的方式。但是不知道性能。
select SUBSTRING_INDEX(GROUP_CONCAT(id order by datetime desc separator ','),',',1) as id, home, MAX(datetime) as 'datetime'
from topten
group by (home)
因为这还没有发布——这在 SQLServer 中有效,并且是我见过的唯一不需要子查询或 CTE 的解决方案——我认为这是解决这类问题的最优雅的方法
SELECT TOP 1 WITH TIES *
FROM TopTen
ORDER BY ROW_NUMBER() OVER (PARTITION BY home
ORDER BY [datetime] DESC)
关于其工作原理的一些说明 - Order By 子句中的窗口函数将计数器应用于每组 home
值,以便具有最高 [datetime] 值的那个接收 1
。
通过 SELECT
ing TOP 1 WITH TIES
,您将选择具有第一个 ROW_NUMBER 值(即 1)的记录,以及具有相同“绑定”ROW_NUMBER 值为 1 的所有其他记录。
因此,您检索每个排名第一的记录的所有数据。
id
、player
和resource
,即对于 home = 10,您可能会得到:3 | 10 | 04/03/2009 | john | 300
换句话说,它不会t 保证结果集中一行的所有列都属于给定主页的 max(datetime)。MAX(id) AS MaxID
添加到内部SELECT
语句然后去在末尾添加另一行AND tt.id = groupedtt.MaxID
。