根据连续出现的情况对数据进行计数和分组

Muh*_*han 2 mysql gaps-and-islands

我很难定义问题,也许你通过我的虚拟数据理解它

我有这个数据:

PK, TaskPK
1, 1    
2, 1     
3, 2    
4, 2    
5, 5    
6, 1 
7, 1     
8, 2 
9, 2   
10, 5    
11, 5
Run Code Online (Sandbox Code Playgroud)

现在我必须数TaskPK一下,我提出这个查询

Select PK, TaskPK, Count(*)
From tbl
Group by TaskPK
Run Code Online (Sandbox Code Playgroud)

带来了这样的结果

TaskPK, Count(*)
1, 4
2, 4
5, 3
Run Code Online (Sandbox Code Playgroud)

但我想要稍微不同的结果

像这样

TaskPK, Count(*)
1, 2
2, 2
5, 1
1, 2
2, 2
5, 2
Run Code Online (Sandbox Code Playgroud)

上面的结果基于连续的数据出现,因为TaskPK从1开始(它分组在一起),然后它改变它2(它分组在一起),然后5(它分组在一起)taskPK。但是当TaskPK再次变为1时,那么它应该单独分组,不与之前出现的1联系起来,这个任务单独计数

这可能吗?

joa*_*olo 5

此类问题是通常所说的差距和孤岛问题的变体您可以在序列中的间隙和岛屿的 SQL中看到很好的概述

循序渐进的方法

这可以通过几个中间步骤来完成。首先,让我们创建一个(派生)表,我们将其存储为t1,找出在哪些点开始一系列新TaskPK值。

CREATE TEMPORARY TABLE t1 AS
SELECT
    PK, TaskPK, 
    case when 
        (SELECT TaskPK 
         FROM tbl t_prv 
         WHERE t_prv.PK < tbl.PK 
         ORDER BY PK DESC 
         LIMIT 1
        ) <=> TaskPK 
    then 0
    else 1 
    end AS start_of_series
FROM
    tbl ;
Run Code Online (Sandbox Code Playgroud)

t1包含:

PK | 任务PK | 系列开始
-: | -----: | --------------:
 1 | 1 | 1
 2 | 1 | 0
 3 | 2 | 1
 4 | 2 | 0
 5 | 5 | 1
 6 | 1 | 1
 7 | 1 | 0
 8 | 2 | 1
 9 | 2 | 0
10 | 10 5 | 1
11 | 11 5 | 0

注意MySQL 中的 <=> 运算符与NOT DISTINCT FROM标准 SQL 中的运算符等效。它允许与 NULL 值以及非 NULL 值进行相等比较。

现在我们知道每个系列从哪里开始,我们可以通过将列的所有当前值和以前的值加在一起(即累积和)来给出每个PKa 。如果没有窗口函数,这必须通过(糟糕的、低效的)子查询来完成。series_numberstart_of_series

我们将结果存储在表中t2

CREATE TEMPORARY TABLE t2 AS
SELECT
    PK, TaskPK, 
    (SELECT 
        sum(start_of_series) 
    FROM 
        t1 
    WHERE 
        t1.PK <= tbl.PK
    ) AS series_number
FROM
    tbl 
;
Run Code Online (Sandbox Code Playgroud)

其内容为t2

PK | 任务PK | 系列号
-: | -----: | ------------:
 1 | 1 | 1
 2 | 1 | 1
 3 | 2 | 2
 4 | 2 | 2
 5 | 5 | 3
 6 | 1 | 4
 7 | 1 | 4
 8 | 2 | 5
 9 | 2 | 5
10 | 10 5 | 6
11 | 11 5 | 6

此时,您要做的只是GROUP BYSeries_number,并取值TaskPK(只需min(TaskPK)完成这项工作),以及count(*)

SELECT
    min(TaskPK) AS TaskPK, count(*) AS count
FROM
    t2
GROUP BY
    series_number
ORDER BY
    series_number
Run Code Online (Sandbox Code Playgroud)

这是您真正想要的结果:

任务PK | 数数
-----: | ----:
     1 | 2
     2 | 2
     5 | 1
     1 | 2
     2 | 2
     5 | 2

将所有内容放在一起

通过替换t2t1的定义,您可以将所有内容放在一个查询中:

SELECT
    min(TaskPK) AS TaskPK, count(*) AS count
FROM
    (SELECT
        PK, TaskPK, 
        (SELECT 
            sum(start_of_series) 
        FROM 
            (SELECT
                PK, TaskPK, 
                case when 
                    (SELECT TaskPK FROM tbl t_prv WHERE t_prv.PK < tbl.PK ORDER BY PK DESC LIMIT 1) <=> TaskPK 
                then 0
                else 1 
                end AS start_of_series 
            FROM
                tbl
            ) AS t1 
        WHERE 
            t1.PK <= tbl.PK
        ) AS series_number
    FROM
        tbl
    ) AS t2
GROUP BY
    series_number
ORDER BY
    series_number
;
Run Code Online (Sandbox Code Playgroud)
任务PK | 数数
-----: | ----:
     1 | 2
     2 | 2
     5 | 1
     1 | 2
     2 | 2
     5 | 2

这个查询非常复杂(其中有五个 SELECT),并且可能仍然可以简化一些。

您可以在dbfiddle找到完整的代码和数据

如果您可以使用MariaDB 的 WINDOW 函数MySQL 8.0 窗口函数(目前尚未发布用于生产),则可以非常简化。