簡體   English   中英

聚合 SQL 中的連續行

[英]Aggregating consecutive rows in SQL

鑒於 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

最好答案不應該要求創建臨時表,因為預計輸出的順序總是與數據插入的順序相同。

您可以使用窗口函數來做到這一點:

  • 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

請參閱演示
結果:

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

這是一種間隙和島嶼問題。 為此,您可以使用行號的差異:

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);

為什么這行得通有點難以解釋。 但是,當您查看子查詢的結果時,它變得非常明顯。 當名稱不變時,相鄰行的行號差異是恆定的。

是一個 db<>fiddle。

我會將 create table 語句更改為以下內容:

CREATE TABLE person(id integer, firstname nvarchar(255), number integer);
  • 您需要第三列來確定插入順序
  • 我會將列名重命名為 firstname 之類的名稱,因為 name 是某些 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

基於 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM