计算非规范化数据库中按 id 和月份分组的条目

Count entries grouped by id and month from denormalized database

我有一个 table (tbl_operations) 的行,其中 id 列值可能是逗号分隔的。我想获得每个月每个 OpId 的计数。我试图通过纯粹的 sql 来实现这一点,但没有成功。

从这个角度来看

OpId OpDate
3 2022-01-03
5,3 2022-01-15
4 2022-01-27
5 2022-02-01
7 2022-02-09
3,2 2022-01-16

至此

OpId count Month
2 1 01
3 3 01
4 1 01
5 1 01
5 1 02
7 1 02

我被困在这里了。有人可以告诉我如何用 sql 做到这一点吗?如果不是,也许使用php来显示结果?

SELECT tbl_operations.OpId,
    tbl_operations.OpDate ,
    COUNT(tbl_operations.OpId) AS `count`
FROM tbl_operations
WHERE MONTH(OpDate)=1
GROUP BY  tbl_operations.OpId

这是一个简单的例子。第一部分只是创建一个数组数组,模拟您从数据库中获得的内容。

要点是 $counts 是一个数组,其中的键具有唯一的 OpID。这些数组的值是 sub-arrays,其中包含月份的键和找到它们的次数的值。

显示应该再次只是一个简单的循环,但是您可能需要对其进行排序。

$rows = [
['3',   '2022-01-03'],
['5,3', '2022-01-15'],
['4',   '2022-01-27'],
['5',   '2022-02-01'],
['7',   '2022-02-09'],
['3,2', '2022-01-16'],
];

$counts = [];
foreach($rows as $row){
    $ids = explode(',', $row[0]);
    $month = date('m', strtotime($row[1]));
    foreach($ids as $id){
        if(!array_key_exists($id, $counts)){
            $counts[$id] = [];
        }
        if(!array_key_exists($month, $counts[$id])){
            $counts[$id][$month] = 0;
        }
        
        $counts[$id][$month]++;
    }
}

此处演示:https://3v4l.org/mVaBB

编辑

来自@mickmackusa,您可以使用 isset:

来缩短内部循环
        if(!isset($counts[$id][$month])){
            $counts[$id][$month] = 0;
        }

查看他们对演示的评论link

如果您要查询 PHP 中的数据,您最好 return 首先使用更好的结果:

SQL

SELECT GROUP_CONCAT(OpId), MONTH(OpDate)
FROM tbl_operations
GROUP BY MONTH(OpDate)

PHP

// Result from MySQL query
$rows = [
    ['3,5,3,4,3,2', 1],
    ['5,7', 2]
];

您可以像这样对这些分组结果进行计数:

$results = [];
foreach ($rows as $row) {
    $counts = array_count_values(explode(',', $row[0]));
    $results[$row[1]] = $counts;
}

结果

Array
(
    [1] => Array
        (
            [3] => 3
            [5] => 1
            [4] => 1
            [2] => 1
        )

    [2] => Array
        (
            [5] => 1
            [7] => 1
        )

)


你真正想做的是规范化你的数据,然后你可以在 SQL 中轻松做到这一点。

如果您至少使用 MYSQL8 并且您不打算规范化您的 table 设计,那么您实际上可以使用以下 CTE 查询来拆分、分组、格式化,并对结果集进行排序(无 PHP 处理)。

这种方法对非规范化 table 进行递归调用,并逐渐将最右边的 id 从 comma-delimited 值中分离出来,并为各个 id 值生成新行。递归一直持续到没有逗号为止。

此解决方案建立在基本技术 demonstrated here 之上。

SQL: (Demo)

WITH RECURSIVE norm AS (
    SELECT OpId,
           OpDate
    FROM tbl_operations
    UNION ALL
    SELECT REGEXP_REPLACE(OpId, '^[^,]*,', '') AS OpId,
           OpDate
    FROM norm
    WHERE OpId LIKE '%,%'
)
SELECT Id,
       Mo,
       COUNT(*) AS Cnt
FROM (
    SELECT REGEXP_REPLACE(norm.OpId, ',.*', '') AS Id,
           MONTH(norm.OpDate) AS Mo
    FROM norm
) formatted
GROUP BY formatted.Id, 
         formatted.Mo

结果集:

Id Mo Cnt
2 1 1
3 1 3
4 1 1
5 1 1
5 2 1
7 2 1

就是说,对于一项 mega-easy 的任务来说,一旦您规范化了 table --- 只需将其规范化 A.S.A.P 即可。