[实践]一道SQL题引发的思考
技术分享

[实践]一道SQL题引发的思考

· 约 2,306 字 · 阅读约 12 分钟
目录

题目

下面的t1表中的数据为用户A的访问记录,请计算每个月的访问的天数。返回 year,month,days 格式。

CREATE TABLE t1 (year YEAR, month INT UNSIGNED,
             day INT UNSIGNED);
INSERT INTO t1 VALUES(2000,1,1),(2000,1,20),(2000,1,30),(2000,2,2),
            (2000,2,23),(2000,2,23);

正确结果返回应该为:

+------+-------+------+
| year | month | days |
+------+-------+------+
| 2000 |     1 |    3 |
| 2000 |     2 |    2 |
+------+-------+------+

答案一

SELECT year,month,BIT_COUNT(BIT_OR(1<<day)) AS days FROM t1 GROUP BY year,month;

答案二

SELECT year, month, COUNT(day) AS days FROM (SELECT DISTINCT year,month,day FROM t1) as t2 GROUP BY year, month;

分析

我的答案

一开始,我想着,先对年月日进行一次去重,因为只需要计算一个月的天数,所以重复的数据是没有用的。

SELECT DISTINCT year,month,day FROM t1;

然后,在上面的结果的基础上,根据年月进行分组计算日的数量。

SELECT year, month, COUNT(day) AS days FROM (SELECT DISTINCT year,month,day FROM t1) as t2 GROUP BY year, month;

标准答案

SELECT year, month, COUNT(day) AS days FROM (SELECT DISTINCT year,month,day FROM t1) as t2 GROUP BY year, month;

然而,看了一眼标准答案,惊为天人,用了两个位运算的函数 BIT_COUNT 与 BIT_OR ,还有左移符号 << 。

看第一眼,没看懂了,看第二眼,还是没有看懂。

  • BIT_COUNT 计算对应的数的二进制中的1的数量,例如
mysql> SELECT BIT_COUNT(4); 
+--------------+
| BIT_COUNT(4) |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

mysql> SELECT BIT_COUNT("64");
+-----------------+
| BIT_COUNT("64") |
+-----------------+
|               1 |
+-----------------+
1 row in set (0.00 sec)
  • BIT_OR 可以理解为按位或,就是对 select出来的字段的值的列表,进行所有按位或操作。
可以这样理解,BIT_OR(column) # column 包含 1,3,4,5
那么最终结果为 1|3|4|5 = 7

继续分析:

  1. 1<<day:这是一个位左移操作。它将数字1向左移动day位。在这里,day很可能是一个代表月份中某一天的字段。例如,如果day是2,那么1<<day的结果是4(二进制中的100),这相当于2的day次方。

  2. BIT_OR(1<<day):BIT_OR是一个聚合函数,它对组内所有行执行位或(bitwise OR)操作。在这个查询中,它将同一个year和month组内的所有1<<day的结果进行位或操作。这样,每个不同的day都会在结果中设置一个位。因为每个月的天数是唯一的,所以这个操作最终会产生一个整数,其二进制表示中的位数表示该月中出现的不同天数。【不超过32天,位数不会超过31,可以使用BIT_OR】

  3. BIT_COUNT(BIT_OR(1<<day)):BIT_COUNT函数计算一个数字的二进制表示中1的数量。在这个查询中,它计算上一步BIT_OR操作结果中的位数,这个位数就是该月中有记录的不同天数。

  4. GROUP BY year,month:这个语句指示MySQL按照年和月来分组记录。对于每个组,上述的位运算将单独执行。

综上所述,这个查询对于每个月份,计算了t1表中有记录的不同天数。这种方法特别适用于处理稀疏数据,即不是每一天都有记录的情况。通过位运算,它能高效地对存在记录的天数进行计数,而不需要存储一个完整的日期列表。

总结

位运算的函数还是挺有效的,学习了。

MySQL :: MySQL 8.0 Reference Manual :: 3.6.8 Calculating Visits Per Day

MySQL :: MySQL 8.0 Reference Manual :: 12.12 Bit Functions and Operators

相关文章