【问题标题】:Aggregating consecutive rows in SQL聚合 SQL 中的连续行
【发布时间】:2021-02-19 22:14:53
【问题描述】:

给定 sql 表(我使用的是 SQLite3):

CREATE TABLE person(name text, number integer);

并填充值:

insert into person values 
('Leandro', 2),
('Leandro', 4),
('Maria',   8),
('Maria',   16),
('Jose',    32),
('Leandro', 64);

我想要的是获得number 列的总和,但仅限于连续行,这样我就可以获得保持原始插入顺序的结果:

Leandro|6
Maria|24
Jose|32
Leandro|64

到目前为止,我得到的“最接近”的是:

select name, sum(number) over(partition by name) from person order by rowid;

但这清楚地表明我对 SQL 的理解还很遥远,因为缺少最重要的功能(连续行的分组和求和),但至少顺序是存在的 :-):

Leandro|70
Leandro|70
Maria|24
Maria|24
Jose|32
Leandro|70

最好答案不应该要求创建临时表,因为预计输出总是与插入数据的顺序相同。

【问题讨论】:

  • 您在两列表格中看到的顺序实际上并不存在。您需要提供表格顺序的第三列。
  • @TimBiegeleisen 我依赖于 SQLite 在未添加主键 ID 时自动添加的隐藏“rowid”主键((整数自动增量))。不是一个好习惯,但对于示例来说已经足够了:-)

标签: sql sqlite aggregate-functions


【解决方案1】:

你可以用窗口函数来做到这一点:

  • LAG() 检查前一个名称是否与当前名称相同
  • SUM() 为连续的同名创建组

然后按组分组并聚合:

select name, sum(number) total
from (
  select *, sum(flag) over (order by rowid) grp
  from (
    select *, rowid, name <> lag(name, 1, '') over (order by rowid) flag
    from person 
  )
)
group by grp

请参阅demo
结果:

> name    | total
> :------ | ----:
> Leandro |     6
> Maria   |    24
> Jose    |    32
> Leandro |    64

【讨论】:

    【解决方案2】:

    这是一种孤岛问题。为此,您可以使用行号的差异:

    select name, sum(number)
    from (select p.*,
                 row_number() over (order by number) as seqnum,
                 row_number() over (partition by name order by number) as seqnum_1
          from person p
         ) p
    group by name, (seqnum - seqnum_1)
    order by. min(number);
    

    为什么这行得通有点难以解释。但是,当您查看子查询的结果时,它变得非常明显。在名称不变的情况下,相邻行的行号差异是恒定的。

    Here 是一个 dbfiddle。

    【讨论】:

      【解决方案3】:

      我会将创建表语句更改为以下内容:

      CREATE TABLE person(id integer, firstname nvarchar(255), number integer);
      
      • 您需要第三列来确定插入顺序
      • 我会将列名重命名为 firstname 之类的名称,因为名称是某些 DBMS 中的关键字。这也适用于名为 number 的列。此外,我会将名称的文本类型更改为 nvarchar,因为它可以按原因在组中排序。

      然后你可以插入你的数据:

      insert into person values 
      (1, 'Leandro', 2),
      (2, 'Leandro', 4),
      (3, 'Maria',   8),
      (4, 'Maria',   16),
      (5, 'Jose',    32),
      (6, 'Leandro', 64);
      

      之后可以通过以下方式查询数据:

      SELECT firstname, value FROM (
          SELECT p.id, p.firstname, p.number, LAG(p.firstname) over (ORDER BY p.id) as prevname,
          CASE
              WHEN firstname LIKE LEAD(p.firstname) over (ORDER BY p.id) THEN number + LEAD(p.number) over(ORDER BY p.id)
              ELSE number
          END as value
          FROM Person p
      ) AS temp
      WHERE temp.firstname <> temp.prevname OR 
      temp.prevname IS NULL
      
      • 首先选择case语句中的值
      • 然后您过滤数据并查看那些先前名称不是实际名称的条目。

      为了更好地理解查询,您可以自己运行子查询:

      SELECT p.id, p.firstname, p.number, LEAD(p.firstname) over (ORDER BY p.id) as nextname, LAG(p.firstname) over (ORDER BY p.id) as prevname,
      CASE
          WHEN firstname LIKE LEAD(p.firstname) over (ORDER BY p.id) THEN number + LEAD(p.number) over(ORDER BY p.id)
          ELSE number
      END as value
      FROM Person p
      

      【讨论】:

      • 您的建议是有效的,但是表结构和名称是人为的,我承认这很随意,只是为了举一个简单的例子。 SQLite 在未手动创建主键(整数自动增量)时添加“隐藏”列(rowid),因此在示例中不需要 id 列。
      【解决方案4】:

      根据 Gordon Linoff 的回答 (https://stackoverflow.com/a/64727401/1721672),我将内部选择提取为 CTE,以下查询运行良好:

      with p(name, number, seqnum, seqnum_1) as
          (select name, number,
              row_number() over (order by number) as seqnum,
              row_number() over (partition by name order by number) as seqnum_1
          from person)
      select
          name, sum(number)
      from
          p
      group by 
          name, (seqnum - seqnum_1)
      order by
          min(number);
      

      产生预期的结果:

      Leandro|6
      Maria|24
      Jose|32
      Leandro|64
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-03
        • 2023-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多