一列中的SQL最大值

时间:2014-03-03 00:12:20

标签: mysql sql max greatest-n-per-group

我正在尝试获取一个特定列(“版本”)中具有最高值的记录。我正在使用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

在一个看起来像这样的表上:

enter image description here

然后我得到以下结果:

enter image description here

这是不正确的,正如您从表中看到的那样。即,它为每个资源获取了最高值,但是如果你看一下引言,例如版本值为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过滤导致的性能损失太大,但这只是一秒钟的延迟,所以仍然比这更好。

但我觉得我错过了一些东西,不应该更有效地做到这一点吗?

7 个答案:

答案 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(版本)。此查询的主要成本是订单,因为没有索引。