我正在尝试获取一个特定列(“版本”)中具有最高值的记录。我正在使用base_id来获取行,并且可能有多个行具有相同的base_id,但它们将具有不同的版本号。所以声明的重点是只获得具有最高版本的那个。以下声明有效,但前提是实际上有多个值。如果只有一个我根本没有记录(而不是预期的一行)。那么我怎样才能获得下面最高版本号的值,即使某些记录只存在一个版本?:
SELECT r.id
, r.title
, u.name created_by
, m.name modified_by
, r.version
, r.version_displayname
, r.informationtype
, r.filetype
, r.base_id
, r.resource_id
, r.created
, r.modified
, GROUP_CONCAT( CONCAT(CAST(c.id as CHAR),',',c.name,',',c.value) separator ';') categories
FROM resource r
JOIN category_resource cr
ON r.id = cr.resource_id
JOIN category c
ON cr.category_id = c.id
JOIN user u
ON r.created_by = u.id
JOIN user m
ON r.modified_by = m.id
WHERE r.base_id = 'uuid_033a7198-a213-11e3-93de-2b47e5a489c2'
AND r.version = (SELECT MAX(r.version) FROM resource r)
GROUP
BY r.id;
编辑:
我意识到查询本身的其他部分可能会使事情变得复杂,所以我会尝试创建一个更清晰的例子,它应该显示我想要的东西,我希望。
如果我这样做:
SELECT id, title, MAX(version) AS 'version' FROM resource GROUP BY title
在一个看起来像这样的表上:
然后我得到以下结果:
这是不正确的,正如您从表中看到的那样。即,它为每个资源获取了最高值,但是如果你看一下引言,例如版本值为2的资源具有id 6,而获取的资源具有id 1.因此查询似乎以某种方式组合来自不同行的值...?
我应该注意到我在SQL上非常新手,我在这里解释问题的原始查询是我在这里得到帮助的,所以请尽可能清楚地解释,谢谢。
另一个注意事项是我发现了一些子查询的建议,但除了没有返回正确的结果之外,它确实很慢。我正在测试5000行,我真的需要它只需要几分之一秒,以满足性能要求。
编辑2:
找到一种方法来整合一个声明,有点像建议的声明,以及这里的各种解决方案:Retrieving the last record in each group
然而,我尝试了所有这些,尽管大多数似乎都有效,但它们的速度非常慢......
拿这个:
SELECT
r.id, r.title,
u.name AS 'created_by', m.name AS 'modified_by', r.version, r.version_displayname, r.informationtype,
r.filetype, r.base_id, r.resource_id, r.created, r.modified,
GROUP_CONCAT( CONCAT(CAST(c.id as CHAR),',',c.name,',',c.value) separator ';') AS 'Categories'
FROM
resource r
INNER JOIN
(SELECT
DISTINCT r.id AS id
FROM
resource r
INNER JOIN
category_resource cr1 ON (r.id = cr1.resource_id)
WHERE
cr1.category_id IN (9)
) mr
ON r.id = mr.id
INNER JOIN category_resource cr
ON r.id = cr.resource_id
INNER JOIN category c
ON cr.category_id = c.id
INNER JOIN user u
ON r.created_by = u.id
INNER JOIN user m
ON r.modified_by = m.id
INNER JOIN
(
SELECT max(version) MyVersion, base_id
FROM resource
GROUP BY base_id
) r2
ON r.base_id = r2.base_id
AND r.version = r2.MyVersion
group by r.base_id
order by r.version desc;
最后添加(从INNER JOIN开始)以仅获取每个base_id具有最高版本值的行,从而将查询从20毫秒减慢到大约6-8秒。这是不行......但这让我感到惊讶。虽然我显然不是数据库专家,但在我看来,应该优化数据库查询以获取这样的数据。但是,如果我做了唯一可以考虑的替代方案,即无论版本号如何都获取所有记录,然后在PHP中过滤它们,猜猜是什么?这比这快得多......
我最初认为PHP过滤导致的性能损失太大,但这只是一秒钟的延迟,所以仍然比这更好。
但我觉得我错过了一些东西,不应该更有效地做到这一点吗?
答案 0 :(得分:1)
根据您自己的答案,您的问题基本上与您提供的链接相同。既然你有一些子问题,我会尝试在那里给你一些额外的帮助。
如果您想在数据库中使用某种版本控制,那么您基本上可以通过某些版本列扩展主键。由于你提到的原因,我也会投票使用startdate / enddate-colums。根据您自己的答案,您可以相应地修改您的布局。如果可以的话,这就是你应该去的路线!
在您给出的示例中,不清楚主键是什么,因为'id'列也具有更改值。在您的情况下,主键是列'标题'。所以你可以使用像
这样的查询SELECT title, max(version) as version FROM resource GROUP BY title
获得一个结果,您可以在其中看到原始主键和最新版本 - 它们共同构成您的实际主键。
要获取该表中的所有其他字段,您需要将该结果连接到资源表,并使用主键字段作为连接条件。
SELECT * FROM (
SELECT title, max(version) as version
FROM resource
GROUP BY title) as s
INNER JOIN resource r on (r.title = s.title AND r.version = s.version)
为什么您的查询会给出错误的结果?
原因是,您的查询中出现错误,MySQL有点“修复”。通常,您需要在GROUP BY子句中提供未在聚合函数中使用的每个列(如MAX())。在你的例子中
SELECT id, title, MAX(version) AS 'version' FROM resource GROUP BY title
您在查询的选择部分中有一个colum('id'),您在GROUP BY子句中未提供该列。
在MySQL中,您可以忽略该规则(see here)。
使用此功能时,每个组中的所有行应与GROUP BY部分中省略的列具有相同的值。服务器可以自由地从组中返回任何值,因此除非所有值都相同,否则结果是不确定的。
由于'id'列的键具有不同的值('title'列),您只得到了一些结果 - 在这种情况下,MySQL可能只使用了它找到的第一行。但结果本身是未定义的,可能会有变化,例如:当数据库更新或数据增长时。您不应该依赖于您在测试时看到的结果推导出的规则!
在oracle和SQL-Server等其他数据库上,如果尝试执行上一次查询,则会出错。
我希望我能澄清你的结果的原因。
答案 1 :(得分:0)
嗯,我想我自己找到了答案。据我所知,这样的查询将花费大量时间,而数据库需要修改。我发现了这个:
How to version control a record in a database
使用startend和enddate列并将enddate设置为null以获取最新版本的建议使得查询最新版本变得非常容易。它再次非常快。所以这就是我需要的。它给了我这样的东西,所有这些都放在一起:
SELECT
r.id, r.title,
u.name AS 'created_by', m.name AS 'modified_by', r.version, r.version_displayname, r.informationtype,
r.filetype, r.base_id, r.resource_id, r.created, r.modified,
GROUP_CONCAT( CONCAT(CAST(c.id as CHAR),',',c.name,',',c.value) separator ';') AS 'categories', startdate, enddate
FROM
resource r
INNER JOIN
(SELECT
DISTINCT r.id AS id
FROM
resource r
INNER JOIN
category_resource cr1 ON (r.id = cr1.resource_id)
WHERE
cr1.category_id IN (9)
) mr
ON r.id = mr.id
INNER JOIN category_resource cr
ON r.id = cr.resource_id
INNER JOIN category c
ON cr.category_id = c.id
INNER JOIN user u
ON r.created_by = u.id
INNER JOIN user m
ON r.modified_by = m.id
WHERE r.enddate is null
group by r.id;
此查询再次回到20 ms执行时间。
答案 2 :(得分:0)
如果你尝试这样的事情怎么办: -
SELECT r.id
, r.title
, u.name created_by
, m.name modified_by
, r.version
, r.version_displayname
, r.informationtype
, r.filetype
, r.base_id
, r.resource_id
, r.created
, r.modified
, GROUP_CONCAT( CONCAT(CAST(c.id as CHAR),',',c.name,',',c.value) separator ';') categories
FROM resource r
JOIN category_resource cr
ON r.id = cr.resource_id
JOIN category c
ON cr.category_id = c.id
JOIN user u
ON r.created_by = u.id
JOIN user m
ON r.modified_by = m.id
WHERE r.base_id = 'uuid_033a7198-a213-11e3-93de-2b47e5a489c2'
AND r.version = (SELECT MAX(r1.version) FROM resource r1 where r1.base_id = r.base_id group by r.base_id)
GROUP
BY r.id;
答案 3 :(得分:0)
与Steve的答案类似,您可以使用以下内容: -
Select
id = (Select id From Resources R2 Where R2.title = R1.title And R2.version = (Select Max(version) From Resources R2 Where R2.title = R1.title)),
R1.title,
version = (Select Max(version) From Resources R3 Where R3.title = R1.title)
From Resources R1
Group By R1.title
Order By R1.title
答案 4 :(得分:0)
尝试使用窗口函数:
SELECT x.* FROM (
SELECT
r.id
, r.title
, u.name created_by
, m.name modified_by
, r.version
, row_indicator=row_number() over (partition by r.base_id order by r.version desc)
, r.version_displayname
, r.informationtype
, r.filetype
, r.base_id
, r.resource_id
, r.created
, r.modified
, GROUP_CONCAT( CONCAT(CAST(c.id as CHAR),',',c.name,',',c.value) separator ';') categories
FROM resource r
JOIN category_resource cr
ON r.id = cr.resource_id
JOIN category c
ON cr.category_id = c.id
JOIN user u
ON r.created_by = u.id
JOIN user m
ON r.modified_by = m.id
WHERE r.base_id = 'uuid_033a7198-a213-11e3-93de-2b47e5a489c2'
) x
where row_indicator = 1
关键部分是使用row_number()窗口函数。如果你查找SQL Server窗口函数,你会发现它们非常强大,在很多情况下都不需要子查询和/或自连接。
要按row_number()(别名为“row_indicator”)进行过滤,您必须将查询包装在内联视图中。由于与row_number()函数一起使用的分区子句按版本降序排序,因此最高版本的版本将收到1的row_number()。
祝你好运!答案 5 :(得分:-1)
我是从SQL Server(2005)的角度写的,但我怀疑它在MySQL中是一样的。
首先,您的示例查询将导致错误:
SELECT id, title, MAX(version) AS 'version' FROM Resource GROUP BY title
消息8120,级别16,状态1,行XX列'Resource.ID'无效 在选择列表中,因为它不包含在聚合中 函数或GROUP BY子句。
添加用于修复错误的ID将提供解释为什么它无法实现目标的线索。如果您在分组中包含该ID,则不会过滤“重复”标题。您可以改为使用MAX(ID),这可能会产生正确的数据,但(1)如果在较早的版本号之后总是插入更高的版本号,那么它只会是可靠的,并且(2)查询会变得更复杂当你添加字段时,因为它们也会参与分组。
相反,您可以在表格中为不同列表中的每个项目找到“TOP”条目。您可以使用以下查询完成此操作:
-- Populate Test Data
DECLARE @Resource TABLE
(
ID int IDENTITY,
Title varchar(100),
Version int
);
INSERT INTO @Resource (Title, Version) VALUES ('Introduction', 1);
INSERT INTO @Resource (Title, Version) VALUES ('Technical Data', 1);
INSERT INTO @Resource (Title, Version) VALUES ('Warranty', 1);
INSERT INTO @Resource (Title, Version) VALUES ('Product Description', 1);
INSERT INTO @Resource (Title, Version) VALUES ('Warranty', 2);
INSERT INTO @Resource (Title, Version) VALUES ('Introduction', 2);
INSERT INTO @Resource (Title, Version) VALUES ('Technical Data', 3);
-- Query with desired results
SELECT
*
FROM @Resource r1
WHERE r1.ID =
(
SELECT
TOP 1 r2.ID
FROM @Resource r2
WHERE r2.Title = r1.Title
ORDER BY r2.Version DESC,
r2.ID DESC
);
如果您可以保证给定标题不会有重复的版本号,您可以使用以下任一方法(每个方法生成相同的查询计划):
SELECT
*
FROM @Resource r1
WHERE r1.Version =
(
SELECT
MAX(r2.Version)
FROM @Resource r2
WHERE r2.Title = r1.Title
)
ORDER BY r1.Title;
SELECT r1.*
FROM (
SELECT
r2.Title,
MAX(r2.Version) AS MaxVersion
FROM @Resource r2
GROUP BY r2.Title
) AS MaxVerList
JOIN @Resource r1
ON r1.Title = MaxVerList.Title
AND r1.Version = MaxVerList.MaxVersion
ORDER BY r1.Title;
答案 6 :(得分:-1)
使用生成的Data Riley,将@更改为临时表的#,再次从SQl Server 2008的角度来看,但它的核心SQL应该可以正常工作,而不会过度导致性能问题。
SELECT
*
FROM #Resource r1
WHERE r1.Version = (SELECT MAX(r2.Version)
FROM #Resource r2 WHERE r1.Title = r2.Title )
ORDER BY r1.ID
给出正确答案
ID Title Version
4 Product Description 1
5 Warranty 2
6 Introduction 2
7 Technical Data 3
你正在寻找我所看到的每个标题的Max(版本)。此查询的主要成本是订单,因为没有索引。