【问题标题】:Group By clause in SQLiteSQLite 中的 Group By 子句
【发布时间】:2019-12-07 01:33:38
【问题描述】:

目标:

我想查询表以仅选择每个项目的最新版本。

问题:

  1. 为什么 Query1SQLite 中工作(我在想group by 子句会抛出错误,因为select 语句包含content 列并且它不是@ 的一部分987654325@ 子句)?
  2. Query1 会在Oracle 中抛出错误吗?
  3. Query1 是否优于 Query2
  4. 有没有更好的方法来编写查询?

查询1:

select item_id, 
       max(version_number), 
       content
from item_version
group by item_id;

查询2:

select iv.*
from item_version iv, 
     (select item_id, 
             max(version_number) latest_version_number
      from item_version
      group by item_id) liv
where iv.item_id = liv.item_id
  and iv.version_number = liv.latest_version_number;

设置表:

create table item_version(
    item_id              varchar,
    version_number       integer,
    content              varchar,
    primary key (item_id, version_number)
);


insert into item_version values (1, 1, null);
insert into item_version values (2, 1, "Content A");
insert into item_version values (2, 2, "Content B");
insert into item_version values (3, 1, "Content C");
insert into item_version values (3, 2, null);
insert into item_version values (4, 1, "Content D");
insert into item_version values (4, 2, null);

【问题讨论】:

    标签: sql sqlite group-by


    【解决方案1】:

    来自the documentation

    在大多数 SQL 实现中,聚合查询的输出列只能引用 GROUP BY 子句中命名的聚合函数或列。在聚合查询中引用普通列没有什么意义,因为每个输出行可能由输入表中的两行或多行组成。

    SQLite 没有施加这个限制。聚合查询的输出列可以是任意表达式,包括在 GROUP BY 子句中找不到的列。

    使用 SQLite(但不是我们知道的任何其他 SQL 实现),如果聚合查询包含单个 min() 或 max() 函数,则输出中使用的列的值取自 min 所在的行() 或 max() 值已达到。如果两行或多行具有相同的 min() 或 max() 值,则将从这些行之一中任意选择列值。

    例如查找薪水最高的员工:

    SELECT max(salary), first_name, last_name FROM employee;

    在上面的查询中,first_name 和 last_name 列的值将对应于满足 max(salary) 条件的行。

    如果查询根本不包含聚合函数,则可以添加 GROUP BY 子句代替 DISTINCT ON 子句。换句话说,输出行被过滤,因此对于 GROUP BY 子句中的每组不同的值只显示一行。如果两个或多个输出行本来具有相同的 GROUP BY 列值集,则任意选择其中一个行。

    您的查询 1 会在大多数数据库中导致错误,是的,但只要您只将它与 sqlite 一起使用,就可以了。


    查找每个项目的最高版本的替代方法是使用 Sqlite 3.25 中添加的窗口函数:

    SELECT item_id, version_number, content
    FROM (SELECT item_id, version_number, content
               , row_number() OVER (PARTITION BY item_id ORDER BY version_number DESC) AS rnk
          FROM item_version) AS sq
    WHERE rnk = 1
    ORDER BY item_id;
    

    给予

    item_id     version_number  content   
    ----------  --------------  ----------
    1           1                         
    2           2               Content B 
    3           2                         
    4           2                         
    

    只要它们也支持窗口函数,它就可以在 Oracle 等其他数据库上运行。

    【讨论】:

    • 感谢您的快速回复,请问有没有更好的方法来编写 query2 ?
    • partition by query 的性能会比 query2 更好吗?
    • @user1046037 我完全不知道。但是对于 sqlite 特定的代码,第一个将比任何一个都更有效。
    • @user1046037 至少在 sqlite 中对窗口函数有很大推动作用的一件事是将主键定义更改为 primary key (item_id, version_number desc)
    • 谢谢@Shawn,我的印象是,partition by 和 order 会比 query2 更贵,我可能完全错了。在插入时添加 version_number desc 索引可能会很昂贵。
    【解决方案2】:

    Shawn 很好地解释了这个问题。解决此问题的典型方法是使用相关子查询:

    select iv.*
    from item_version iv
    where iv.version_number = (select max(iv2.version_number)
                               from item_version iv2
                               where iv2.item_id = iv.item_id
                              );
    

    使用item_version(item_id, version_number) 上的索引,这可能是获得所需结果的最快方法。您的主键定义已经有了这个索引。

    【讨论】:

    • 谢谢,难道没有主键也可以作为item_idversion_number 的索引吗?
    • @user1046037 。 . .非常真实。我在答案中澄清了。
    猜你喜欢
    • 2012-04-20
    • 2018-03-23
    • 2022-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    • 2011-07-13
    • 1970-01-01
    相关资源
    最近更新 更多