最近工作上遇到一个需求,类似将每位客户到访记录的访问打卡记录统计出来。a表关键字段如下:
id |
customer_id |
customer_name |
morning |
afternoon |
123456 |
123456 |
测试 |
83514,111547 |
134512,170230 |
后面两个字段分别记录客户上午下午的到访时间,现要通过到访时间去查询到访时的照片取证。熟悉的小伙伴看到可能就想到,咱们把morning和afternoon两个字段合并起来in一次不就可以了么。所以就有了如下的SQL:(假设b表为照片信息表)
SELECT t1.id,t1.customer_id,t1.customer_name,t2.picture_url FROM a t1, b t2 WHERE FIND_IN_SET(t2.time, CONCAT(IF(t2.morning='','',IF(t2.afternoon='',t2.morning, CONCAT(t2.morning,','))),IF(t2.afternoon='','',t2.afternoon)));
诚然用上面的SQL对付小数据量的表完全有效,但是数据量上万就完犊子了,哪怕a表针对morning、afternoon制定索引也于事无补。毕竟where条件中的函数会导致索引失效嘛,FIND_IN_SET函数会无法命中索引~ 现实这么残酷,我当时直接就
那是不可能的,于是有了这么一段SQL:
SELECT 到访ID, 客户ID, 客户name, t2.picture_url
FROM (SELECT tmp.*,
SUBSTRING_INDEX(SUBSTRING_INDEX(tmp.ma, ',', b.help_topic_id ), ',',-1) `time`
FROM (SELECT t.id `到访ID`, t.dm_id `客户ID`, t.dm_name `客户name`,
concat( t.morning, ',', t.afternoon ) `ma` FROM a t) tmp
JOIN help_topic b ON b.help_topic_id <= (LENGTH(tmp.time)-LENGTH(REPLACE(tmp.time, ',', '' )) + 1 )) t1
INNER JOIN b t2 ON t1.time = t2.time
通过上述SQL可以将a表数据结构转换成:
id |
customer_id |
customer_name |
time |
123456 |
123456 |
测试 |
83514 |
123456 |
123456 |
测试 |
111547 |
123456 |
123456 |
测试 |
134512 |
123456 |
123456 |
测试 |
170230 |
这样将行转为列是不是就可以一一对应了呢~
急于使用的小伙伴拿熟肉去改改就行,接下来笔者对于SQL中部分知识点进行梳理:
FIND_IN_SET(截图来自Mysql中文文档)
图示中很清晰的告知使用者FIND_IN_SET函数可以将字段中类似于set且分隔符只能为逗号(半角)的值进行分割,然后对目标字符串进行查找返回目标字符串所在位置。
PS:如果字段是其他分隔符,请使用replace函数将分隔符改为逗号。
TableA,TableB
如上所示的表连接方式是交叉连接,它类似于:
select * from TableA cross join TableB ~ select * from TableA,TableB
结果:TableA的行数*TableB的行数的结果集。
SUBSTRING_INDEX函数(截图来自Mysql中文文档)
可以将SUBSTRING_INDEX函数理解为两个函数,一个是查找delim定界符,一个是截取字符串。从上图中count为2时可以知道,SUBSTRING_INDEX先查找从左往右数的第二个定界符,找到之后从左往右截取字符串直到搜寻到的定界符(不包含定界符)。
此方法在SQL中的作用请查看第五点
help_topic表
Mysql中有四大help table:help_topic、help_relation、help_category、help_keyword。
翻了翻Mysql的官方文档没看到详尽解释,下面的help语法概述摘抄自
(作者:沃趣科技 链接:https://zhuanlan.zhihu.com/p/42076758)
help 语法支持3种模式的匹配查询:查看所有主题顶层类别或子类别、查看帮助主题下的关键字、使用给定主题下的唯一关键字查看帮助信息,这些信息分表保存在 help_category、help_topic、help_keyword表,help_relation表存放help_topic与help_keyword表中信息的映射信息。下面将针对这几张表的基础知识进行简单的科普。
(1)help_category
该表提供查询帮助主题的类别信息,每一个类别分别对应着N个帮助主题名或者主题子类别名,通过查询表中的信息我们也可以看出来,如下:
root@localhost : mysql 01:10:59> select * from help_category;
+------------------+-----------------------------------------------+--------------------+-----+
| help_category_id | name | parent_category_id | url |
+------------------+-----------------------------------------------+--------------------+-----+
| 1 | Geographic | 0 | |
| 2 | Polygon properties | 35 | |
......
| 39 | Functions | 36 | |
| 40 | Data Definition | 36 | |
+------------------+-----------------------------------------------+--------------------+-----+
40 rows in set (0.00 sec)
表字段含义
help_category_id:帮助主题名称或子类别名称在表中的记录ID
name:帮助主题类别名称或字类别名称
parent_category_id:父主题类别名称在表中的记录ID,一些主题类别具有子主题类别,例如:绝大多数的主题类别其实是Contents类别的子类别(且是顶层类别,也是一级父类别),还有一部分是Geographic Features 类别的子类别(二级父类别),一部分是Functions的子类别(二级父类别)
url :对应在MySQL 官方手册中的链接地址
(2)help_keyword
该表提供查询与帮助主题相关的关键字字符串信息,如下:
root@localhost : mysql 01:12:07> select * from help_keyword limit 5;
+-----------------+---------+
| help_keyword_id | name |
+-----------------+---------+
| 681 | (JSON |
| 486 | -> |
| 205 | ->> |
| 669 | <> |
| 521 | ACCOUNT |
+-----------------+---------+
5 rows in set (0.00 sec)
表字段含义
help_keyword_id:帮助关键字名称在表中记录对应的ID
name:帮助关键字字符串
(3)help_relation
该表提供查询帮助关键字信息和主题详细信息之间的映射,用于关联查询help_keyword与help_topic表,如下:
root@localhost : mysql 01:13:09> select * from help_relation limit 5;
+---------------+-----------------+
| help_topic_id | help_keyword_id |
+---------------+-----------------+
| 0 | 0 |
| 535 | 0 |
| 294 | 1 |
| 277 | 2 |
| 2 | 3 |
+---------------+-----------------+
5 rows in set (0.00 sec)
表字段含义
help_topic_id:帮助主题详细信息ID,该ID值与help_topic表中的help_topic_id相等
help_keyword_id:帮助主题关键字信息ID,该ID值与help_keyword表中的help_keyword_id相等
(4)help_topic
该表提供查询帮助主题给定关键字的详细内容(详细帮助信息),如下:
root@localhost : mysql 01:13:31> select * from help_topic limit 1\G;
*************************** 1. row ***************************
help_topic_id: 0
name: JOIN
help_category_id: 28
description: MySQL supports the following JOIN syntaxes for the table_references
part of SELECT statements and multiple-table DELETE and UPDATE
statements:
table_references:
escaped_table_reference [, escaped_table_reference] ...
escaped_table_reference:
table_reference
| { OJ table_reference }
......
url: http://dev.mysql.com/doc/refman/5.7/en/join.html
1 row in set (0.00 sec)
表字段含义
help_topic_id:帮助主题详细信息在表记录中对应的ID
name:帮助主题给定的关键字名称,与help_keyword表中的name字段值相等
help_category_id:帮助主题类别ID,与help_category表中的help_category_id字段值相等
description:帮助主题的详细信息(这里就是我们通常查询帮助信息真正想看的内容,例如:告诉我们某某语句如何使用的语法与注意事项等)
example:帮助主题的示例信息(这里告诉我们某某语句如何使用的示例)
url:该帮助主题对应在MySQL官方在线手册中的URL链接地址
LENGTH(tmp.time)-LENGTH(REPLACE(tmp.time, ',', '' )) + 1
意为获取字段长度减去去除定界符长度后加一的长度,这其实是为了与help_topic_id对比获取字段
原始SQL:
JOIN help_topic b ON b.help_topic_id <= (LENGTH(tmp.time)-LENGTH(REPLACE(tmp.time, ',', '' )) + 1 )) t1
首先加一是为了抵消咱们通过concat拼接逗号导致定界符加一的问题
举一个例子:
假设morning和afternoon都为空的话,concat函数将字段组合之后值变为( , )。原始SQL执行之后会得到:
id |
customer_id |
customer_name |
ma |
time |
help_topic_id |
123456 |
123456 |
测试 |
, |
1 |
|
123456 |
123456 |
测试 |
, |
2 |
假设morning为空,afternoon不为空,concat函数将字段组合之后值变为(83514, )。原始SQL执行之后会得到:
id |
customer_id |
customer_name |
ma |
time |
help_topic_id |
123456 |
123456 |
测试 |
83514, |
83514 |
1 |
123456 |
123456 |
测试 |
83514, |
2 |
假设morning不为空为空,afternoon为空,concat函数将字段组合之后值变为(,111547)。原始SQL执行之后会得到:
id |
customer_id |
customer_name |
ma |
time |
help_topic_id |
123456 |
123456 |
测试 |
,111547 |
1 |
|
123456 |
123456 |
测试 |
,111547 |
111547 |
2 |
假设morning不为空,afternoon不为空,concat函数将字段组合之后值变为(83514,111547)。原始SQL执行之后会得到:
id |
customer_id |
customer_name |
ma |
time |
help_topic_id |
123456 |
123456 |
测试 |
83514,111547 |
83514 |
1 |
123456 |
123456 |
测试 |
83514,111547 |
111547 |
2 |
由上可知SQL通过使用help_topic表存储由特定定界符分割的字段,如果没有值的话就会返回空字符串。这里也就告诉我们下面的SQL其实获取了定界符分割出来原始字段个数(空字符串也算一个)
LENGTH(tmp.time)-LENGTH(REPLACE(tmp.time, ',', '' )) + 1
经过上面我们知道help_topic_id其实就是代表分割出来的字段标识,再通过第三点了解SUBSTRING_INDEX函数的用法就可以知道下面SQL其实就是为了得到具体分割字段的值:
SUBSTRING_INDEX(SUBSTRING_INDEX(tmp.ma, ',', b.help_topic_id ), ',',-1)
假设例子为下面的表格大家可以试试看是否可以得出time的值
id |
customer_id |
customer_name |
ma |
time |
help_topic_id |
123456 |
123456 |
测试 |
83514,111547 |
83514 |
1 |
123456 |
123456 |
测试 |
83514,111547 |
111547 |
2 |
end~