校招面试题有了!——记录一个有趣的mySql查询

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: ## 前言最近在做一个内部系统的报表统计功能,遇到了一个麻烦的查询场景,因为对sql语句确实不太熟练,在网上查了一些资料,最终找到了一个解法。具体场景和表结构并不复杂,对sql大佬们来说应该也是小case,不过细想确实有趣,特此记录下来。## 问题已知表A有列:主键id、日期date和标签tag,其中tag是逗号分割的多值字符串(值的集合size不超过25个),如下如示例。请统计某段时间(例

前言

最近在做一个内部系统的报表统计功能,遇到了一个麻烦的查询场景,因为对sql语句确实不太熟练,在网上查了一些资料,最终找到了一个解法。具体场景和表结构并不复杂,对sql大佬们来说应该也是小case,不过细想确实有趣,特此记录下来。

问题

已知表A有列:主键id、日期date和标签tag,其中tag是逗号分割的多值字符串(值的集合size不超过25个),如下如示例。请统计某段时间(例如20220101-20220131)内,每个tag的记录数。

id date tag
1 20220101 1
2 20220111 1,2
3 20220121 1,2,3
4 20220131 4
5 20220101 5,8

分析

思路上是把tag中的数据拆分开,然后去统计。比如把上面的行能拆分成如下的多行,然后再进行统计。

id date tag
1 20220101 1
2 20220111 1
2 20220111 2
3 20220121 1
3 20220121 2
3 20220121 3
4 20220131 4
5 20220101 5
5 20220101 8

解法

网上通常解法

作为不怎么直接写mySql、语法都要查字典的同学,当然先去网上扒一下大家有没有遇到相关问题,是怎么解决的。经过多次搜索,找到了一个相关问题——“MySql一行拆多行”,大家可以自行搜索。
适配场景后的sql如下

SELECT
  t.id,
  t.date,
  substring_index(
    substring_index(
      t.tag,
      ',',
    b.help_topic_id + 1 
  ),
  ',',- 1 
) AS tag 
FROM
  test t
  JOIN 
     mysql.help_topic b 
  ON 
     b.help_topic_id < 
     ( length( t.tag ) - length( REPLACE ( t.tag, ',', '' ) ) + 1 )

分析

直接改造后,在idb上试跑了一下,并没有生成预期的拆分表,翻阅了其他资料,基本都是这一解法,那么先理解这一段sql的含义,再看哪里不work
首先看下里面几个函数和一个特殊的表

  • substring_index:按分隔符截取字符串,substring_index(“待截取有用部分的字符串”,“截取数据依据的字符”,截取字符的位置N);N>0,从前向后截取,N<0,从后向前截取
  • length:字符串长度
  • REPLACE:替换字符串中的内容
  • mysql.help_topic:是mysql 'HELP'指令使用的四张表之一,help_topic_id是这张表的连续自增主键,从0开始

那么上面sql拆分理解下:

length( t.tag ) - length( REPLACE ( t.tag, ',', '' ) ) + 1 
  • 前半部分是计算了字符串中分隔符的个数,+1是字符串中分割出来的单值的个数。
substring_index(
    substring_index(
      t.tag,
      ',',
    b.help_topic_id + 1 
  ),
  ',',- 1 
) AS tag
  • 这部分是把多值tag按照分割符,进行两次分割,分割出对应的单值。

问题升级

如果mysql.help_topic可以使用的话,其实问题到上面应该就结束了。奈何idb上无法使用mysql.help_topic表(具体原因不详,可能是DBA把这个表权限给关了)。
又走不通,那么我们再来深入理解一下mysql.help_topic在这里究竟是起到了什么作用。
想知道它有什么作用也比较简单,其实改造一下sql就一目了然了:

SELECT
  t.id,
  t.date,
  t.tag,
  b.help_topic_id
FROM
  test t
  JOIN 
     mysql.help_topic b 
  ON 
     b.help_topic_id < 
     ( length( t.tag ) - length( REPLACE ( t.tag, ',', '' ) ) + 1 )

拿到的数据应该如下:

id date tag help_topic_id
1 20220101 1 0
2 20220111 1,2 0
2 20220111 1,2 1
3 20220121 1,2,3 0
3 20220121 1,2,3 1
3 20220121 1,2,3 2
4 20220131 4 0
5 20220101 5,8 0
5 20220101 5,8 1

这里是用了help_topic_id从0开始连续增长的特性,在join时,匹配出和tag中值个数相等的多条记录,同时作为每条记录中取多值tag中的第几个值的索引。
理解到这里,这个解法的整体思路就很清晰了,mysql.help_topic能不能用并不是关键了,找一个其他的能从0或1连续自增的表来担任这个功能就ok,大部分的主键id自增的表都可以,甚至可以自己创建一个表,只要能保证连续的个数大于tag中多值的总个数即可。

WHAT IF

既然走到了这,那么不妨再加一点约束条件,如果没有其他的表可用,或者没有其他的表能确保是连续自增,且个数大于tag多值的总个数,怎么办呢(毕竟其他的表随时可能会改,不知道这段依赖的话,改出来都是坑)?或者,我们不专门创建一个新的表(创建新表的话,不知道具体原因的同学,看到也是满脸疑惑——为啥会有这个表???),要怎么解决这个问题呢?
继续查资料后,发现可以使用mySql的自定义变量,生成一组连续的数字代替(MySQL生成连续数字)。下面的sql会生成从1-50的连续数字

SELECT @xi:=@xi+1 as xc from 
            (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1, 
            (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10) xc2,  
            (SELECT @xi:=0) xc0 

最后我的sql变成了这样

SELECT `tag`,COUNT(*)
from(
        SELECT info.`id`, substring_index(substring_index(info.`tag`, ",", b.id), ",", -1)  as tag
            FROM(
                SELECT * from test
                where
                      `tag` IS NOT NULL
                        and `date` >= #{startDate}
                        and `date` <= #{endDate}
                )  as t
            join(
            SELECT @xi:= @xi+ 1 as id
                from
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5)  xc1,
                    (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5)  xc2,
                    (SELECT @xi:= 0)  xc0
                ) b
        on b.id <= (length(t.`tag`)  - length( replace(t.`tag`, ",", '')) + 1)
        ) result
        GROUP BY `tag`;

最终的输出应该是:

| tag | count |
| :----:| :----: |
| 1 | 3 |
| 2 | 2 |
| 3 |1 |
| 4 | 1 |
| 5 | 1 |
| 8 |1 |

写在最后

标题起得标题党了,比较少遇到这样的case,解决这个问题过程学到了不少骚操作,感觉挺有意思的。现在的解法并没有考虑性能问题,有更棒的sql欢迎分享。
另外,表设计很重要,不然后面就会遇到这样的奇葩问题,改表结构的话又是非常难受的工作量。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
20天前
|
存储 关系型数据库 MySQL
美团面试:MySQL为什么 不用 Docker部署?
45岁老架构师尼恩在读者交流群中分享了关于“MySQL为什么不推荐使用Docker部署”的深入分析。通过系统化的梳理,尼恩帮助读者理解为何大型MySQL数据库通常不使用Docker部署,主要涉及性能、管理复杂度和稳定性等方面的考量。文章详细解释了有状态容器的特点、Docker的资源隔离问题以及磁盘IO性能损耗,并提供了小型MySQL使用Docker的最佳实践。此外,尼恩还介绍了Share Nothing架构的优势及其应用场景,强调了配置管理和数据持久化的挑战。最后,尼恩建议读者参考《尼恩Java面试宝典PDF》以提升技术能力,更好地应对面试中的难题。
|
3天前
|
SQL 关系型数据库 MySQL
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
1.请解释什么是MVCC,它在数据库中的作用是什么? 2.在MySQL中,MVCC是如何实现的?请简述其工作原理。 3.MVCC是如何解决读-写和写-写冲突的? 4.在并发环境中,当多个事务同时读取同一行数据时,MVCC是如何保证每个事务看到的数据版本是一致的? 5.MVCC如何帮助提高数据库的并发性能?
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL基础篇】多表查询(隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询)
本文详细介绍了MySQL中的多表查询,包括多表关系、隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询及其实现方式,一文全面读懂多表联查!
【MySQL基础篇】多表查询(隐式/显式内连接、左/右外连接、自连接查询、联合查询、标量/列/行/表子查询)
|
1月前
|
存储 SQL 关系型数据库
MySQL 面试题
MySQL 的一些基础面试题
|
29天前
|
缓存 关系型数据库 MySQL
【深入了解MySQL】优化查询性能与数据库设计的深度总结
本文详细介绍了MySQL查询优化和数据库设计技巧,涵盖基础优化、高级技巧及性能监控。
249 0
|
2月前
|
存储 Oracle 关系型数据库
索引在手,查询无忧:MySQL索引简介
MySQL 是一款广泛使用的关系型数据库管理系统,在2024年5月的DB-Engines排名中得分1084,仅次于Oracle。本文介绍MySQL索引的工作原理和类型,包括B+Tree、Hash、Full-text索引,以及主键、唯一、普通索引等,帮助开发者优化查询性能。索引类似于图书馆的分类系统,能快速定位数据行,极大提高检索效率。
78 8
|
2月前
|
SQL 关系型数据库 MySQL
MySQL 窗口函数详解:分析性查询的强大工具
MySQL 窗口函数从 8.0 版本开始支持,提供了一种灵活的方式处理 SQL 查询中的数据。无需分组即可对行集进行分析,常用于计算排名、累计和、移动平均值等。基本语法包括 `function_name([arguments]) OVER ([PARTITION BY columns] [ORDER BY columns] [frame_clause])`,常见函数有 `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()`, `SUM()`, `AVG()` 等。窗口框架定义了计算聚合值时应包含的行。适用于复杂数据操作和分析报告。
131 11
|
6月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
3月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!

热门文章

最新文章

推荐镜像

更多