查企企官方网站品牌推广公司排行榜
news/
2025/9/22 21:00:21/
文章来源:
查企企官方网站,品牌推广公司排行榜,商业空间设计案例,企业做网站还是做平台好作者简介 多肉#xff0c;饿了么资深python工程师。曾在17年担任饿了么即时配送众包系统的研发经理#xff0c;这篇文章最早的版本就诞生于那段时间#xff0c;目前负责配送相关业务系统的整体稳定性建设。个人比较喜欢c和python#xff0c;最近有点迷rust#xff0c;同时… 作者简介 多肉饿了么资深python工程师。曾在17年担任饿了么即时配送众包系统的研发经理这篇文章最早的版本就诞生于那段时间目前负责配送相关业务系统的整体稳定性建设。个人比较喜欢c和python最近有点迷rust同时还是个archlinux的日常用户有类似爱好的欢迎交流 PrefaceIntroduction1 预备知识 1.1 数据库和数据表1.2 最基本的Sql查询语法 2 更进一步 2.1 IN 操作2.2 关系运算符AND 和 OR2.3 排序ORDER BY 3 高级一点的话题 3.1 聚合函数COUNTSUM, AVG3.2 对查询结果去重DISTINCT 语法3.3 将查询数据分组GROUP BY 语法3.4 聚合函数的好搭档HAVING 语法 4 有点超纲的话题 4.1 字段类型4.2 索引4.3 JOIN语法家族4.4 嵌套的SELECT语法 5 闯关答题快速复习
Preface
为什么以《写给运营同学和初学者的Sql入门教程》为题
这原本是给一位关系要好的运营同学定制的Sql教程。在饿了么总部运营的同学在排查、跟踪线上问题和做运营决策的时候除了通过运营管理系统查询信息和依赖数据分析师给出的分析数据常常也需要直接从数据库管理台通过写Sql的方式获取更细致、更实时的业务数据并基于这些数据进行一些及时的分析从而更快的给出运营方案。在这样的背景下Sql已经越来越成为我们运营同学的一项必备技能。网上有很多Sql教程(e.g. w3school)我也翻阅过一些运营同学桌上的Sql纸质书这些教程都很好但普遍侧重介绍语法很多很多的语法配以简短的demo。作为老司机的reference book很赞可是对于刚入门甚至还没有入门的学习者就未免困难了一点。再回顾运营同学的使用场景大多数情况下是根据一些已有条件做一些简单的查询偶尔会有一些相对复杂的查询比如对查询结果做一些聚合、分组、排序或是同时查询两三张数据表除此以外建表、建索引、修改表字段、修改字段值等等这些操作在运营同学的日常工作中基本是不会遇到的。
基于以上种种原因写了这篇教程初衷是能够帮助这位好朋友以更高的ROI入门Sql。下面是写这篇教程时的一些考量
从数据库、数据表的基础概念以及最简单的Sql语法开始做一些必要的铺垫后续的每一章再逐步增加难度只介绍查询语法(更准确的说是最常用的查询语法)将它们的用法和套路解释清楚不涉及日常工作暂时还用不到的建表、修改表结构等等操作避免铺天盖地的语法一个接一个反而干扰了学习的节奏通过穿插一些小测验、小温习及时检验对知识点的理解和复习已经学习过的知识点结合一些业务场景的demo帮助理解结尾提供一章快速复习既是复习也可以自测还有哪些知识点没有掌握到位
建议所有阅读教程的同学都尝试搭建一套自己的数据库服务(建议安装MySQL)对教程中的demo多做一些练习不论是示例、小测验还是小温习里面的Sql语句都不妨亲自执行一下这也是一种很好的帮助你熟悉语法的方式。当然搭建自己的数据库会是一个不小的挑战写作这篇教程的时候我在自己的VPS上安装了MySQL(MariaDB)并提供了一个连接脚本(隐藏了连接MySQL的过程)给朋友使用但是这种方式并不适合推广到所有人。具体的安装和使用方式不在本教程的叙述范围内所以...运营妹子们可以求助下熟悉的研发同学汉子们嘛..
数据创建脚本-通过该脚本导入demo数据到MySQL中链接VPS上MySQL的脚本-基本原理是建立了一个ssh tunnel在tunnel里和远端的MySQL通信但实际上本地建MySQL服务更方便仅供参考
可以从这里sql_tutorial下载通过pandoclatex导出的pdf获得更好的阅读体验。 B.T.W 由饿了么技术社区主办的首届物流技术开放日终于来啦 时间2018年12月30日 地点饿了么上海总部普陀区近铁城市广场北座5楼榴莲酥 此次活动邀请到了物流团队的6位重量级嘉宾。不仅会有前后端大佬分享最新的架构、算法在物流团队的落地实战经验更有 P10 大佬教你如何在业务开发中获得技术成长。当然也会有各种技术书籍纪念品拿到手软最后最重要的一点完全免费还等什么赶快点击 etech.ele.me/salon.html?… 了解更多细节并报名吧 ?我也会在这次开放日活动中分享Gunicorn有关的话题欢迎大家报名参加。放出一张分享内容的Outline by 多肉
Introduction
其实Sql并没有那么难。Sql是帮助你和关系型数据库交互的一套语法主要支持的操作有4类
DQL其实就是数据查询操作通过特定的语法查询到数据库里你想要的数据结果并且展示出来DML数据修改操作包括更新某个数据字段、向某张表里面插入一条新的数据等等DDL数据定义操作比如创建一张新的表或是创建一个新的索引(索引是啥我们后面专门聊一聊它)DCL数据授权操作比如授权特定的人可以查询某张特定的表
听起来挺吓人的对吧但实际上DML、DDL、DCL这3类操作在日常的运营工作中几乎都不会用到经常会使用到的呐其实是第一种也就是数据查询操作(DQL)。Sql基本的查询语法也比较简单那么难在哪里呢我猜想难在学习了基本语法之后不知道怎么应用到实际的Case上。在接下来的内容里我将以一些十分接近现实的众包运营Case为例逐一解释最基本的Sql查询语法并且分析如何将它应用到具体的场景上。
1 预备知识
好的吧吹了一波牛还是逃不过需要介绍一些最基础的东西但是我保证这是整篇教程中最枯燥的部分后面就会有趣很多。
1.1 数据库和数据表
为了更简单的理解这两个概念以及他们之间的关系可以这么类比
数据表就是一张表格想象一张你经常在使用的Excel表有行有列每一行就是一条数据每一列对应了这条数据的某一个具体的字段当然这张表还有一个表名。数据表也是如此只是字段名、表名不像Excel表格那样好理解比如Excel表格里面某一列的字段名可能叫骑手id而对应到数据表里面可能就叫做rider_idExcel的表名可能叫骑手基本信息表而对应到数据表的表名则可能叫tb_rider数据库就是集中管理一批相关的表格的地方。你可以把它理解成是一个文件夹。平时你可能会创建一个名叫众包业务的文件夹然后将众包运营相关的Excel表格都放在这个文件夹里面。数据库也是如此比如我们会有一个库名叫做crowd的数据库里面可能存放了tb_rider、tb_order等和骑手、运单相关的多张数据表
所以“关系型数据库”的概念很吓唬人但其实道理很简单就是列和列之间有一定的联系整合在一起就是一条有意义的数据将这些数据归纳起来就构成了一张表而将一批有关联的表一同管理起来就得到了一个数据库。
1.2 最基本的Sql查询语法
最基本的Sql查询语法其实就一个
SELECT 列名(或者*表示所有列) FROM 表名 WHERE 筛选条件;
复制代码B.T.W 注意SELECT...FROM...WHERE...;语句结尾的这个分号在标准Sql语法中这个分号是必要的 让我们按照FROM、WHERE、SELECT的顺序理解一下这个语法
FROM 表名顾名思义就是从表名指定的这张表格中WHERE 筛选条件意思是“当满足筛选条件”的时候SELECT 列名意思是选择出这些记录并且展示指定的列名
串联起来便是从FROM后面指定的数据表中筛选出满足WHERE后面指定条件的数据并且展示SELECT后指定的这几列字段。是不是很简单呐不过好像抽象了一点。所以我们来看几个具体的超简单的例子。假设我们有一张学生数学期末考试成绩表数据表长下面这样表名叫作tb_stu_math_score。
id(自增主键)name(学生姓名)number(学号)grade(年级)class(班级)score(得分)1柯南010201121002小哀010202121003光彦01020312984步美01020412955元太0102051259
让我们试着理解一下下面几个查询语句
[1] SELECT name FROM tb_stu_math_score WHERE score 95;
从tb_stu_math_score表中挑选出得分大于95分的学生姓名得到的结果显而易见
name柯南小哀光彦步美
[2] SELECT name, number FROM tb_stu_math_score WHERE score 60;
从tb_stu_math_score表中挑选出得分小于60分的学生姓名得到的结果是
namenumber元太010205
[3] SELECT * FROM tb_stu_math_score WHERE score 100;
从tb_stu_math_score表中挑选出得分为100分学生的所有信息(注意SELECT后面的*符号表示所有字段)得到的结果是
idnamenumbergradeclassscore1柯南010201121002小哀01020212100
小测验
看看下面这些Sql查询语句你是不是知道是什么含义并且知道查询结果是什么了呢
1. SELECT name, grade, class, score FROM tb_stu_math_score WHERE number 010201;
2. SELECT * FROM tb_stu_math_score WHERE name 小哀;
3. SELECT id, score FROM tb_stu_math_score WHERE number 010202;
复制代码
2 更进一步
刚刚我们学习了Sql查询的最最最最基础的语法但是相信我所有的Sql查询几乎都长这个样子所以理解了这个最基础的语法结构后面学习起来就轻松多了。接下来让我通过一些例子扩展这个基础语法教你一些更加高级的Sql查询操作。不过首先我们还是要看一下接下来我们的范例数据表长啥样。
假设我们有一张骑手数据表表名叫作tb_rider还有一张运单数据表表名叫作tb_order这两张表分别长下面这个样子。
[1] 骑手数据表tb_rider
idnamereal_name_certify_statelevellevel_cityis_deletedcreated_atupdated_at1Stark23102017-01-01 22:00:192018-01-01 06:40:012Banner23902017-04-28 12:01:192018-01-01 06:40:013Rogers22102017-04-10 17:24:012018-01-01 06:40:014Thor10102017-12-31 23:10:392018-01-01 06:40:015Natasha21102017-02-11 15:03:132018-01-01 06:40:016Barton21902017-02-11 15:04:192018-01-01 06:40:017Coulson23902017-01-03 23:00:222018-01-01 06:40:018Coulson10202017-01-05 10:10:232018-01-01 06:40:01
字段含义
id自增主键。又是一个听起来很吓人的名字但实际含义很简单。“自增”的意思是每次在这张数据表中创建一条新记录的时候数据库都会在上一个id值的基础上自动加上一个固定的步长(默认就是1)作为新记录的id值。而所谓“主键”就是能够在一张数据表中唯一标识一条记录的字段因为每条记录的id都不一样所以它是主键。这个字段可以是为了单纯的标识数据的唯一性也可以具有一些业务含义比如这里的id就同时也是骑手的账号idname: 骑手姓名real_name_certify_state: 实名认证状态1-认证中2-认证通过3-认证失败level骑手等级3-金牌2-银牌1-铜牌0-普通level_city等级城市;is_deleted这条数据是否有效在大多数产线相关的数据表中都会有这样的标记字段。0-未删除(表示有效), 1-已删除(表示无效)created_at这条数据的创建时间也是所有产线数据表中都必须有的字段updated_at这条数据最新一次被更新的时间同样是所有产线数据表中都必须有的字段
[2] 运单数据表tb_order
idorder_idorder_staterider_idrider_namegrabbed_timecreated_atupdated_at1300000201712300001401Stark2017-12-30 12:34:552017-12-30 12:34:172017-12-30 12:39:302300000201712300002401Stark2017-12-30 12:34:562017-12-30 12:34:182017-12-30 12:44:273300000201712300003402Banner2017-12-30 13:23:122017-12-30 13:20:022017-12-30 13:54:094300000201712300004405Natasha2017-12-30 13:35:032017-12-30 13:34:192017-12-30 14:03:175300000201712300005401Stark2017-12-30 16:01:222017-12-30 16:01:032017-12-30 16:08:216300000201712300006403Rogers2017-12-30 16:10:452017-12-30 16:08:572017-12-30 16:34:277300000201712310001206Barton2017-12-31 09:12:572017-12-31 09:12:072017-12-31 09:20:358300000201712310002807Coulson2017-12-31 09:15:012017-12-31 09:10:332017-12-31 09:20:179300000201712310003802Banner2017-12-31 09:20:172017-12-31 09:18:102017-12-31 09:22:2410300000201712310004203Rogers2017-12-31 10:37:332017-12-31 10:34:012017-12-31 10:38:09113000002017123100051001970-01-01 00:00:002017-12-31 19:29:022017-12-31 19:29:02123000002017123100061001970-01-01 00:00:002017-12-31 19:29:272017-12-31 19:29:27133000002017123100071001970-01-01 00:00:002017-12-31 19:30:012017-12-31 19:30:01
字段含义
id自增主键。呐这里的id就是单纯的主键作用没有其他的业务含义order_id运单号业务层面上运单的唯一标识order_state运单当前的状态。10-待抢单20-待到店80-待取餐40-已送达rider_id抢单的骑手id还未被抢的运单这个字段是默认值0rider_name抢单的骑手姓名还未被抢的运单这个字段是默认值空字符grabbed_time抢单时间还未被抢的运单这个字段是默认的1970-01-01 00:00:00(这是一个特殊的时间有兴趣的话可以搜索关键词时间戳)created_at这条数据的创建时间也是所有产线数据表中都必须有的字段updated_at这条数据最新一次被更新的时间同样是所有产线数据表中都必须有的字段
小温习
试着理解看看下面这几条Sql的含义以及返回的数据结果吧
1. SELECT name, real_name_certify_state FROM tb_rider WHERE level 3;
2. SELECT * FROM tb_order WHERE rider_id 1;
3. SELECT rider_id, rider_name, order_id, grabbed_time FROM tb_orderWHERE order_state 40;
复制代码
2.1 IN 操作
场景 线下反馈了一批骑手说自己理应是上海的金牌但是牌级是普通或者展示的是金牌却没有享受到上海的金牌活动你已经知道了这几个分别是id(2, 4, 7)的骑手想排查一下他们的等级更新情况。
这时你可以选择像这样一条一条的查询像之前我们介绍的那样
1. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id2;
2. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id4;
3. SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id7;
复制代码
这样当然可以达到目的但是只有两三个骑手的时候还勉强可以操作如果有几十个骑手这样查起来就太费劲了。这时候我们可以使用IN这个语法。
SELECT name, real_name_certify_state, level, level_city FROM tb_rider WHERE id IN(2, 4, 7);
复制代码
很简单的对吧但我们还是来简单理解一下WHERE id IN(2, 4, 7)的意思就是筛选id字段的值在247这几个值当中的记录执行这条Sql语句你就会得到下面这样的结果。
namereal_name_certify_statelevellevel_cityBanner239Thor101Coulson239
于是你会发现Thor这个骑手因为他没有通过实名认证所以肯定评不上金牌Banner和Coulson两位骑手虽然都是金牌骑手但是等级城市却是福州所以享受不到上海金牌的活动。
那如果不知道骑手id只知道骑手的名字怎么办也可以使用IN查询只是这时候筛选的条件变成了name取值范围也变成了Banner, Thor, Coulson。就像这样。
SELECT name, real_name_certify_state, level, level_city FROM tb_rider
WHERE name IN(Banner, Thor, Coulson);
复制代码
于是你顺利的得到了以下的结果。
namereal_name_certify_statelevellevel_cityBanner239Thor101Coulson239Coulson102
Oops! 居然有两个Coulson
这就是在实际应用中要特别注意的地方了 当你使用类似骑手id这种被设计为唯一值的字段作为查询依据时返回的结果也是唯一的而当你使用类似骑手姓名这类字段作为查询依据时就有可能出现上面这种情况。这时候你就需要依赖更多的信息来判断哪一条才是你真正想要的。所以能够用明确的字段作为查询依据时就要尽可能的使用。 2.2 关系运算符AND 和 OR
最常用的关系运算符有两个AND和OR用来连接多个筛选条件。顾名思义AND就是**“并且”的意思也就是同时满足AND前后两个筛选条件OR就是“或者”**的意思也就是满足OR前后任何一个筛选条件。有点抽象了对不对我们看一个具体的例子。
场景 假设你想要看看2017-02-01(包括2017-02-01当天)到2017-06-01(不包括2017-06-01当天)期间注册的骑手所有信息。
注册时间对应到数据上就是骑手信息的创建时间(created_at)换句话说就是查询tb_rider表中创建时间处于2017-02-01到2017-06-01之间的数据。那这样的Sql应该怎么写呢这时我们就可以用到AND。
SELECT * FROM tb_rider WHERE created_at 2017-02-01 00:00:00
AND created_at 2017-06-01 00:00:00;
复制代码B.T.W 注意因为包括2017-02-01当天而不包括2017-06-01当天所以前者是而后者是。 让我们再来推广一下。假设现在的场景变成想看一看2017-02-01(包括当天)之前或者2017-06-01(包括当天)之后注册的骑手所有信息。我们应该怎么写这个Sql呢既然是或的关系我们就应该使用OR了。
SELECT * FROM tb_rider WHERE created_at 2017-02-01 00:00:00
OR created_at 2017-06-01 00:00:00;
复制代码B.T.W 注意这里既包括了2017-02-01当天又包括了2017-06-01当天所以前者是后者是。 当然啦AND和OR这样的关系运算符不仅仅能够连接前后两个筛选条件也可以通过使用若干个AND和OR连接多个不同的筛选条件。比如想要看看2017-02-01(包括2017-02-01当天)到2017-06-01(不包括2017-06-01当天)期间注册的且当前是金牌等级的骑手所有信息那么我们可以这么写。
SELECT * FROM tb_rider
WHERE created_at 2017-02-01 00:00:00
AND created_at 2017-06-01 00:00:00
AND level 3;
复制代码
2.3 排序ORDER BY
让我们先小小的复习一下上面学到的知识点有一个这样的场景 我们打算看一下Stark这位骑手在2017-12-30当天抢单且当前状态为已完成的运单号和运单的创建时间。 如何写这个Sql呢先思考3s...1...2...3看看是否和你想的一样。
SELECT order_id, created_at FROM tb_order
WHERE rider_id 1
AND grabbed_time 2017-12-30 00:00:00
AND grabbed_time 2017-12-31 00:00:00
AND order_state 40;
复制代码
如果你没有写对没关系让我们来分析一下
Stark这位骑手的骑手id是1所以我们的第一个筛选条件为rider_id 1因为我们要看2017-12-30当天抢单的运单id确定了我们的第二个筛选条件是抢单时间对应的是grabbed_time这个字段而2017-12-30当天实际上指的就是2017-12-30 00:00:00(包括)到2017-12-31 00:00:00(不包括)这段时间最后是已完成这个条件order_state字段标识了运单状态因此我们的筛选条件是order_state 40
执行这个语句我们得到了下面这样的结果。
order_idcreated_at3000002017123000012017-12-30 12:34:173000002017123000022017-12-30 12:34:183000002017123000052017-12-30 16:01:03
有点美中不足我想按照运单的创建时间倒序排序把最近创建的运单排在最前面这时候就可以使用ORDER BY语法了。
SELECT order_id, created_at FROM tb_order
WHERE rider_id 1
AND grabbed_time 2017-12-30 00:00:00
AND grabbed_time 2017-12-31 00:00:00
AND order_state 40
ORDER BY created_at DESC;
复制代码
让我们再来理解一下DESC是**“递减**的意思与之对应的是ASC递增。ORDER BY created_at DESC的含义是按照(BY)created_at字段值递减(DESC)的顺序对查询结果排序(ORDER)。于是我们得到如下的结果。
order_idcreated_at3000002017123000052017-12-30 16:01:033000002017123000022017-12-30 12:34:183000002017123000012017-12-30 12:34:17 B.T.W 在现实场景中有时候查询结果的集合会很大(例如几百行、几千行)但是我们只想看其中前10行的数据这时候我们可以使用LIMIT语法。例如这里我们可以使用LIMIT语法仅仅展示前两行查询结果 SELECT order_id, created_at FROM tb_order WHERE rider_id 1 AND grabbed_time 2017-12-30 00:00:00 AND grabbed_time 2017-12-31 00:00:00 AND order_state 40 ORDER BY created_at DESC LIMIT 2; 我们再来看一个更加复杂的场景假设想要查询2017-12-30和2017-12-31两天所有运单的所有信息并先按照骑手id递增再按运单状态递减的顺序排序展示。还是先思考一会儿。
这时的Sql类似长这样。
SELECT * FROM tb_order
WHERE created_at 2017-12-30 00:00:00
AND created_at 2018-01-01 00:00:00
ORDER BY rider_id ASC, order_state DESC;
复制代码
如果前面的每个知识点都理解了这里应该就只对**“先按照骑手id递增再按运单状态递减的顺序排序展示”**有所疑惑。实际上我们不仅可以对一个字段排序还可以把多个字段作为排序的依据而且不同字段上的排序规则(递增/递减)可以不同。但排序是有优先级的比如这里只有当rider_id字段的值都相同无法区分顺序时才会对相同rider_id的这几条数据再按照order_state字段的值进行排序。举例来说rider_id 2且order_state 80的数据也依然不可能排在rider_id 1且order_state 40的数据前面。
执行这条Sql语句将得到的结果如下。
idorder_idorder_staterider_idrider_namegrabbed_timecreated_atupdated_at113000002017123100051001970-01-01 00:00:002017-12-31 19:29:022017-12-31 19:29:02123000002017123100061001970-01-01 00:00:002017-12-31 19:29:272017-12-31 19:29:27133000002017123100071001970-01-01 00:00:002017-12-31 19:30:012017-12-31 19:30:011300000201712300001401Stark2017-12-30 12:34:552017-12-30 12:34:172017-12-30 12:39:302300000201712300002401Stark2017-12-30 12:34:562017-12-30 12:34:182017-12-30 12:44:275300000201712300005401Stark2017-12-30 16:01:222017-12-30 16:01:032017-12-30 16:08:219300000201712310003802Banner2017-12-31 09:20:172017-12-31 09:18:102017-12-31 09:22:243300000201712300003402Banner2017-12-30 13:23:122017-12-30 13:20:022017-12-30 13:54:096300000201712300006403Rogers2017-12-30 16:10:452017-12-30 16:08:572017-12-30 16:34:2710300000201712310004203Rogers2017-12-31 10:37:332017-12-31 10:34:012017-12-31 10:38:094300000201712300004405Natasha2017-12-30 13:35:032017-12-30 13:34:192017-12-30 14:03:177300000201712310001206Barton2017-12-31 09:12:572017-12-31 09:12:072017-12-31 09:20:358300000201712310002807Coulson2017-12-31 09:15:012017-12-31 09:10:332017-12-31 09:20:17
这个部分相对有一点难可以多对比着例子理解一下。
3 高级一点的话题
进入到这个部分说明之前的内容你基本都已经掌握了在日常运营的操作中有30%左右的场景都可以使用前面讲述的这些知识点解决(当然会有个熟能生巧的过程)。这个部分我将继续介绍几个更加高级、当然也更加有难度的Sql技能当你结束这一部分的学习并且熟练掌握这些技能的时候你会发现绝大部分需要通过查数据来确认的场景你都可以胜任。因为这个章节的内容本身难度又大了些如果再对着一张复杂的表就更加难以关注重点因此我们精简一下表结构只保留一些必要的字段。新的tb_order表如下。
idorder_idorder_staterider_idrider_namemerchant_customer_distancecreated_at1300000201712300001401Stark2.52017-12-30 12:34:172300000201712300002401Stark1.82017-12-30 12:34:183300000201712300003402Banner1.82017-12-30 13:20:024300000201712300004405Natasha2.72017-12-30 13:34:195300000201712300005401Stark1.22017-12-30 16:01:036300000201712300006403Rogers0.52017-12-30 16:08:577300000201712310001206Barton1.32017-12-31 09:12:078300000201712310002807Coulson2.92017-12-31 09:10:339300000201712310003802Banner0.72017-12-31 09:18:1010300000201712310004203Rogers2.22017-12-31 10:34:01113000002017123100051000.32017-12-31 19:29:02123000002017123100061001.32017-12-31 19:29:27133000002017123100071003.02017-12-31 19:30:01
新增的列 merchant_customer_distance配送距离(商家到用户的直线距离)单位是千米(km)。
3.1 聚合函数COUNTSUM, AVG
千万别被聚合函数这个名字吓唬到可以简单的理解为对数据进行一些加工处理让我们先来分别看一下这几个聚合函数的基本定义。
COUNT对查询结果集合中特定的列进行计数SUM对查询结果的某个字段进行求和AVG就是average的意思对查询结果的某个字段计算平均值
让我们分别来看几个具体的例子。
[1] 场景查询2017-12-30这一天骑手Stark的所有完成单(状态为40)总量
你可以这样来写这个Sql。
SELECT COUNT(id) FROM tb_order WHERE rider_id 1
AND order_state 40 AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00;
复制代码
到这里你应该已经能够很好的理解WHERE...AND...AND...这部分的含义我们就不再过多的讨论这个部分(对自己要有信心试着理解先自己理解一下)。
让我们重点来看一下COUNT(id)这部分的含义。其实很简单就是对id这一列进行计数。连起来看这段Sql意思就是从tb_order这张表中(FROM tb_order)筛选(WHERE)骑手id为1(rider_id 1)且运单状态为已完成(order_state 40)且创建时间大于等于2017年12月30日(created_at 2017-12-30 00:00:00)且创建时间小于2017年12月31日(created_at 2017-12-31 00:00:00)的数据并且按照id这列对返回的结果集合进行计数。
我们看到tb_order这张表中2017-12-30当天由骑手Stark配送且状态是已完成的运单分别是300000201712300001、300000201712300002、300000201712300005这几个运单号的运单对应的自增id分别是id[1, 2, 5]所以对id这一列进行计数得到的结果是3。所以我们得到的查询结果如下表。
COUNT(id)3
有时候你仅仅是想查一下满足某个条件的记录的总行数而并非想对某个特定的列进行计数这时就可以使用COUNT(*)语法。比如上面的这个Sql也可以写成下面这个样子。
SELECT COUNT(*) FROM tb_order WHERE rider_id 1
AND order_state 40 AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00;
复制代码
因为返回的结果有三行所以我们会得到下表的结果。
COUNT(*)3
看起来COUNT(列)和COUNT(*)是完全等价的有些特定的场景下的确如此这里需要补充一下COUNT的两个小脾气。
COUNT不会自动去重COUNT在某一条查询结果中用来计数的那一列的值为**“空**时这条记录不进行计数 B.T.W 注意这里的“空”指的是null而不是某一列没有展示出任何值就是空这是一个相对技术的概念当前不理解可以先跳过 有一点晕是吗不着急我们来看两个例子。假设有两张表很简单的表长下面这样。
示例表1tb_sample_1
idname1Stark2Stark3Coulson4Natasha5Stark
示例表2tb_sample_2
idname1Stark2Stark3null4null5Natasha6Coulson
我们下猜一猜下面几条Sql的执行结果分别是什么
1. SELECT COUNT(id) FROM tb_sample_1;
2. SELECT COUNT(*) FROM tb_sample_1;
3. SELECT COUNT(name) FROM tb_sample_1;
4. SELECT COUNT(name) FROM tb_sample_2;
复制代码B.T.W 当SELECT...FROM...WHERE...语句中的WHERE...部分被省略时表示查询表中的所有数据(不对数据进行筛选)。 让我们逐一分析一下。
1. SELECT COUNT(id) FROM tb_sample_1;
复制代码
这条Sql没有太多可以分析的因为tb_sample_1表中id字段的取值范围是id[1, 2, 3, 4, 5]共5个所以我们得到的结果如下。
COUNT(id)5
2. SELECT COUNT(*) FROM tb_sample_1;
复制代码
这条Sql也没有太多需要分析的因为COUNT(*)的含义是计算查询结果的总行数tb_sample_1共5行数据所以我们得到的结果如下。
COUNT(*)5
3. SELECT COUNT(name) FROM tb_sample_1;
复制代码
这条Sql里面我们对name这一列进行计数tb_sample_1表中包含3个Stark1个Coulson和1个Natasha因为COUNT不进行自动去重因此结果是53(Stark)1(Coulson)1(Natasha)如下表。
COUNT(name)5
4. SELECT COUNT(name) FROM tb_sample_2;
复制代码
这条Sql语句我们还是对name这一列进行计数tb_sample_2表中包含2个Stark1个Coulson1个Natasha以及2个null由于COUNT不去重因此2个Stark都会被计数但COUNT不会对值为**“空”**的结果进行计数因此两个null都会被忽略。所以最终的结果为42(Stark)1(Coulson)1(Natasha)如下表。
COUNT(name)4
[2] 场景查询Stark这名骑手的累计配送里程
让我们先定义一下累计配送里程骑手所有配送完成单的配送距离(商家到用户的直线距离)之和。
这里的关键词是求和所以我们要用到SUM这个聚合函数。对字段求和的意思是把返回的结果集合中该字段的值累加起来。让我们看下这个场景的Sql怎么写。
SELECT SUM(merchant_customer_distance) FROM tb_order
WHERE rider_id 1 AND order_state 40;
复制代码
让我们来分析一下这条语句FROM tb_order WHERE rider_id 1 AND order_state 40已经比较好理解了就是从tb_order表中筛选出骑手id为1且配送状态为40的记录。而这里的SUM(merchant_customer_distance)的含义就是对前面的条件筛选出的数据结果中的merchant_customer_distance列的值进行求和。根据骑手id和配送状态筛选出的记录分别为id(1, 2, 5)对应的merchant_customer_distance的值分别为merchant_customer_distance(2.5, 1.8, 1.2)求和结果为5.52.51.81.2如下表。
SUM(merchant_customer_distance)5.5
[3] 场景查询Stark这名骑手的平均配送里程
同样的让我们先来定义一下平均配送里程骑手所有完成单的配送距离(商家到用户的直线距离)之和除以总的完成单量。
基于SUM的经验和前面的“预告”不难想到这次我们会用到AVG这个聚合函数。对字段求平均值的意思是把结果集合中该字段的值累加起来再除以结果总行数。AVG帮我们自动完成了“做除法”的动作所以Sql的长相和上一个场景的SUM是如出一辙的。
SELECT AVG(merchant_customer_distance) FROM tb_order
WHERE rider_id 1 AND order_state 40;
复制代码
根据骑手id和配送状态筛选出的记录分别为id(1, 2, 5)对应的merchant_customer_distance的值分别为merchant_customer_distance(2.5, 1.8, 1.2)求平均值的结果为1.83(2.51.81.2) / 3如下表。
AVG(merchant_customer_distance)1.83
写在3.1节的最后
对着这几个场景学习下来不知道你感觉怎么样吖是否觉得这几个聚合函数本身还蛮简单的或者也有可能会觉得一下子灌输了很多知识点有点费劲呢其实聚合函数有它复杂的一面我们上面看的这些Case都是比较简单的使用方式。但是千万不要担心一方面是因为运营工作中遇到的绝大多数场景都不会比这些示例Case更复杂另一方面是不鼓励过于复杂的使用这些聚合函数因为查询的逻辑越是复杂就越是难以“预测”查询的结果Sql并不是一个适合表达“逻辑”的语言如果对数据的再加工逻辑很多就应该考虑像分析师提需求或者学习更加利于表达逻辑的其他编程语言。
其次要说的就是多给自己些信心同时也要多一点耐心。Sql虽然不同于Python、Java这样的通用编成语言除了语法还杂糅着一套体系化的编程概念、设计哲学但是初次上手的时候还是会感觉到有些吃力的。但是只要多去理解几遍示例、多自己写一写特别是在之后遇到实际工作中真实场景的时候自己思考如何转化为Sql、多实践、多回顾分析很快就会在潜移默化中掌握它要相信熟能生巧。
接下来的3.2、3.3节我会继续介绍两个实用的Sql语法以及如何将它们和聚合函数结合使用会更有难度一些。
3.2 对查询结果去重DISTINCT 语法
DISTINCT语法顾名思义就是对某一列的值进行去重让我们首先来回顾一下3.1节中COUNT的其中一个例子。
这个例子使用的是tb_sample_1这张表这张表很简单让我再把它贴出来。
idname1Stark2Stark3Coulson4Natasha5Stark
对应的我们想要回顾的这条Sql语句也很简单。
SELECT COUNT(name) FROM tb_sample_1;
复制代码
前面我们已经分析过这条Sql对name这列进行计数有3个Stark1个Coulson1个Natasha所以得到最终的结果如下表。
COUNT(name)5
可是有的时候我们不想对相同的名字进行重复计数当有多个相同的名字时只计数一次。这时候就可以使用到DISTINCT语法。
SELECT COUNT(DISTINCT name) FROM tb_sample_1;
复制代码
对比上一条Sql只是增加了一个DISTINCT关键字其实理解起来呢也不用把它想的太复杂啦COUNT(DISTINCT name)就是对去重后的name进行计数。tb_sample_1中有3个Stark,但是3个Stark是重复的使用DISTINCT语法后只会被计算一次另外还有1个Coulson和一个Natasha所以得到的结果如下表。
COUNT(DISTINCT name)3
DISTINCT语法可以单独使用这时就是它本身的意思对某列的值进行去重。但是相比之下更常见的是像上面的例子一样和COUNT这个聚合函数一起使用这样就可以对去重后的结果进行计数。
3.3 将查询数据分组GROUP BY 语法
前面我们基于tb_order这张表讲解了很多Sql的语法知识让我们再来回忆一下这张表的容颜。
idorder_idorder_staterider_idrider_namemerchant_customer_distancecreated_at1300000201712300001401Stark2.52017-12-30 12:34:172300000201712300002401Stark1.82017-12-30 12:34:183300000201712300003402Banner1.82017-12-30 13:20:024300000201712300004405Natasha2.72017-12-30 13:34:195300000201712300005401Stark1.22017-12-30 16:01:036300000201712300006403Rogers0.52017-12-30 16:08:577300000201712310001206Barton1.32017-12-31 09:12:078300000201712310002807Coulson2.92017-12-31 09:10:339300000201712310003802Banner0.72017-12-31 09:18:1010300000201712310004203Rogers2.22017-12-31 10:34:01113000002017123100051000.32017-12-31 19:29:02123000002017123100061001.32017-12-31 19:29:27133000002017123100071003.02017-12-31 19:30:01
温故而知新先来出几道题目复习一下前面所学的Sql知识。
复习题1 试着写出以下几个场景对应的Sql语句
查询2017-12-30当天创建的运单状态为已完成且配送距离大于等于2公里的总单量查询2017-12-30当天创建且状态为已完成的所有运单的平均配送距离查询2017-12-30当天完成过配送任务(至少配送完成1单)的骑手总人数
复习题2 试着理解以下几条Sql的含义并且写出查询的结果
1. SELECT COUNT(order_id) FROM tb_order WHERE order_state 40AND merchant_customer_distance 2.0 AND created_at 2017-12-30 00:00:00AND created_at 2017-12-31 00:00:00;
2. SELECT AVG(merchant_customer_distance) FROM tb_order WHERE order_state 40AND created_at 2017-12-30 00:00:00 AND created_at 2017-12-31 00:00:00;
3. SELECT COUNT(DISTINCT rider_id) FROM tb_order WHERE order_state 40AND created_at 2017-12-30 00:00:00 AND created_at 2017-12-31 00:00:00;
复制代码
聪明的你是否发现复习题2就是复习题1的答案呢如果还没有发现没关系再回过头来多分析几遍Practice Makes Perfect 绝对是真理。不过复习这几个例子可不仅仅是为了复习哦让我们在1、2两个场景的基础下扩展一下讲解新的知识点。思考下面这两个场景。
查询2017-12-30当天每个参与跑单的骑手各自的完成单总量查询2017-12-30当天每个参与跑单骑手的完成单平均配送距离
首先分析一下这里的场景1。“2017-12-30当天”这个条件不难转化为created_at 2017-12-30 00:00:00 AND created_at 2017-12-31 00:00:00“完成单”不难转化为order_state 40由于要计算运单的“总量”我们也不难想到可以对order_id进行COUNT操作。那么如何分组到每个骑手身上呢这时候就要用到GROUP BY了。
SELECT COUNT(order_id) FROM tb_order WHERE order_state 40
AND created_at 2017-12-30 00:00:00 AND created_at 2017-12-31 00:00:00
GROUP BY rider_id;
复制代码
注意这里执行顺序是先按照WHERE条件进行筛选然后根据骑手id进行分组(GROUP BY)最后再对每个分组按照运单号进行计数。因此我们可以得到下表的结果。
COUNT(order_id)3111
好像有哪里不对结果中看不到对应的骑手吖不着急我们稍微修改下刚才的Sql将骑手id、骑手姓名这2列展示出来就可以了。
SELECT rider_id, rider_name, COUNT(order_id)
FROM tb_order WHERE order_state 40
AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00
GROUP BY rider_id;
复制代码
我们得到如下表的结果。
rider_idrider_nameCOUNT(order_id)1Stark32Banner15Natasha13Rogers1
这样是不是就清晰多了。
再来分析场景2。有了前面的例子“2017-12-30当天”、“完成单”这两个条件应该是已经得心应手、信手拈来了“平均配送距离”问题也不大可以转化为AVG(merchant_customer_distance)。那么如何分组到每个骑手身上呢还是通过GROUP BY语法。我们的Sql长成下面这个样子。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state 40
AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00
GROUP BY rider_id;
复制代码
得到如下表的结果。
rider_idrider_nameAVG(merchant_customer_distance)1Stark1.832Banner1.85Natasha2.73Rogers0.5
还是需要特别提一下这里的执行顺序首先执行的是WHERE条件筛选然后对筛选出的数据结果根据骑手id进行分组最后再对每个分组中的数据进行merchant_customer_distance列的求平均值。
3.4 聚合函数的好搭档HAVING 语法
HAVING语法的含义类似于WHERE当我们使用HAVING的时候一般遵循HAVING 筛选条件的语法结构。你可能会问啦既然和WHERE语法含义差不多、使用方式又很类似那干嘛还要凭空多个HAVING语法出来呢原因就在于聚合函数。WHERE语法是不能和聚合函数一起使用的但有些时候我们却需要依赖聚合函数的计算结果作为筛选条件。让我们看一下3.3节中场景2这个例子。
场景2查询2017-12-30当天每个参与跑单骑手的完成单平均配送距离。
通过前面我们的分析得到这样的Sql。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state 40
AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00
GROUP BY rider_id;
复制代码
我们在场景2的基础上再扩展一下。
扩展的场景2查询2017-12-30当天每个参与跑单骑手的完成单平均配送距离并筛选出其中平均配送距离超过1.5km的数据。
我们得到这样的Sql结果。
SELECT rider_id, rider_name, AVG(merchant_customer_distance)
FROM tb_order WHERE order_state 40
AND created_at 2017-12-30 00:00:00
AND created_at 2017-12-31 00:00:00
GROUP BY rider_id
HAVING AVG(merchant_customer_distance) 1.5;
复制代码
比较一下不难发现变化仅仅是末尾多了HAVING AVG(merchant_customer_distance) 1.5这条子句。让我们分析看看。SELECT ... FROM ... WHERE ...和之前的用法并没有变化GROUP BY rider_id将SELECT的结果根据rider_id进行分组分组完成后HAVING AVG(merchant_customer_distance) 1.5语句对每一组的merchant_customer_distance字段值求取平均数并且将平均数大于1.5的结果筛选出来作为返回结果。
执行这条Sql我们得到结果。
rider_idrider_nameAVG(merchant_customer_distance)1Stark1.832Banner1.85Natasha2.7
Rogers这位骑手(骑手id3)因为平均配送距离为0.5不满足HAVING语句指定的“平均配送距离大于1.5km”的筛选条件所以没有在我们的查询结果中。
4 有点超纲的话题
4.1 字段类型
类型这个词此刻你听起来可能还是很陌生的但其实在计算机科学领域类型是一个非常基础而且广泛存在的概念几乎每一种编程语言都有自己的类型系统。 B.T.W 在二进制中每一个0或者1被称作一个比特位所以32位是指一段二进制数据中0和1的个数加在一起共有32个例如00000000000000000000000000000001表示一个32位二进制数0000000000000001表示一个16位二进制数。 [1] 为什么要定义类型的概念
关于为什么要有类型这个概念我呐有一个“不成熟”的理解编程语言作为人和机器交互的一种工具人类对数据有人类逻辑上的理解当我们看到2903的时候我们会认为这是个整数当我们看到1031.2903的时候我们会认为这是个小数。而机器在处理数据或者存取数据的时候是无差别的按照比特位进行二进制运算或者读写的。人类很难做到直接用二进制输入计算机当然也不能接受计算机直接以二进制的形式输出结果。设想一下如果某天咱们想用一下电脑上的计算器计算个112但是我们没有类型我们需要理解机器是如何处理二进制的那么就可能需要输入00000000000000000000000000000001 00000000000000000000000000000001而得到的结果也是二进制00000000000000000000000000000010这得多累人呐。有了类型就轻松多了通过定义数据的类型根据类型的约定计算机就知道如何将这个1转化为二进制(包括应该转化为16位、32位还是64位的二进制对这段二进制数据进行操作的时候应该把它看作整数还是浮点数等等)而返回结果的时候也就知道如何将二进制的00000000000000000000000000000010转化为我们能够理解的整数2
编程语言的类型其实就是人与机器约定好的去理解和操作数据的一套规则。
总而言之在机器的眼里无论是对数据进行何种操作它看到的都是一串一串由0和1构成的东西称呼这种东西有专门的术语叫作**“字节流”或者“二进制流“**。
让我们再一起看一个例子。假设要处理这样的一段二进制流00000000100111011000001111010111这段二进制流可以表示很多东西要明确它的含义就需要明确它的类型比如下面这两种不同的类型这段流表示的内容就完全不同。
如果我们把这段二进制流看作是32位整型那么它代表的是10322903这个整数如果我们把这段二进制流看作是2个16位整型(前16位0000000010011101表示一个整型后16位1000001111010111表示一个整型)那么它分别代表的是157和33751这两个整数
我知道你此刻对为何转换为32位整型是10322903为何看作2个16位整型转换后是157和33751还有着很多疑惑。但是关于二进制和十进制的转换方法呢在这里就不做展开了如果你很感兴趣、很想知道可以再单独给你讲这个方法。讲上面的这些最主要的还是希望你明白定义“类型”的概念根本上是在人机交互的过程中提供了一种机制赋予无差别的二进制流一定的语义。
还是太抽象了对不对没关系我们再来举个栗子。
前面我们在预备知识这一章中使用到了tb_stu_math_score这张表为了不让你辛苦的再翻回去我们再贴一下这张表的内容啦。
id(自增主键)name(学生姓名)number(学号)grade(年级)class(班级)score(得分)1柯南010201121002小哀010202121003光彦01020312984步美01020412955元太0102051259
也写过类似下面这条Sql语句。
SELECT score FROM tb_stu_math_score WHERE id1;
复制代码
这条Sql语句非常非常的简单现在我们已经知道它会返回第一行数据score这一列的值结果长下面这样。
score100
让我们分析一下获取这个结果的整个流程帮助你理解一下类型是如何发挥作用的。
这条Sql语句会被执行根据主键id找到对应的行进而获取到这一行、score这列的值但是我们上面说到计算机的存储是基于二进制的所以获取到的score值是类似于00000000000000000000000001100100这样的二进制流这时候根据tb_stu_math_score表的定义score这一列被定义为整型于是将二进制流转化为一个整型数经过进制转换得到00000000000000000000000001100100对应的整型值为100于是我们看到的结果就是100。
实际上反过来也非常类似当我们向这张表中写入数据时例如写入的score列的值为100。因为存储基于二进制根据表的定义score列的类型为整型于是将值100按照整型转换为对应的二进制流00000000000000000000000001100100并且写入到库中。
[2] Sql的主要数据类型有哪些
Sql中常常接触的数据类型主要包括几类。
1 整型
tinyint用来表示很小很小的整数比如常常用它作为is_deleted、is_valid这些字段的字段类型因为这两个字段表示该条记录是否有效只存在两个值分别是0和1smallint比tinyint稍微大一点点的整型可以表示更大一点的整数比如200、404、401这样的整数值int常用的整型可以用来表示比较大的整数比如10322(事实上int可以表示的整数范围远远比这个大)bigint用来表示非常大的整数比如大多数表的自增id就会使用这个类型可以表示类似10322903这样非常大的整数(事实上bigint可以表示的整数范围远远比这个要大)
2 浮点型
decimal可以表示非常准确的小数比如经纬度
3 字符串类型
char固定长度的字符串varchar可变长度的字符串
这里固定长度和可变长度指的是数据库中的存储形式因为这部分的内容其实有些超出了这个教程的范围我们不过多的解释这里的区别。一般在我们实际的应用中varchar用的更多一些。它们都表示类似于very glad to meet u, Huohuo!这样的一串字符当然也可以是中文敲开心认识你火火。
4 日期类型
date表示一个日期只包含日期部分不包含时间比如当前日期2018-01-23datetime表示一个日期同时包含日期部分和时间部分比如当前日期2018-01-23 03:01:43
我们在这里只是简单的介绍了几种Sql中常见的字段类型并没有很深入的去解释它们的原理、差异以及一些其他的数据类型咱们不着急去学习那些“高大上”的内容先理解这些类型的含义。
[3] 怎么知道一张表中每一列的类型是什么
第1种方式是使用DESC 表名命令例如我们想看一下之前提到的tb_rider表的每一列字段类型就可以执行命令DESC tb_rider得到下面的结果。
FieldTypeNullKeyDefaultExtraidint(11)NOPRInullauto_incrementnamevarchar(32)NOreal_name_certify_stateint(11)NO0is_deletedtinyint(4)NO0created_atdatetimeNOMULCURRENT_TIMESTAMPupdated_atdatetimeNOMULCURRENT_TIMESTAMPon update CURRENT_TIMESTAMPleveltinyint(4)NO0level_cityvarchar(32)NO
注意这里的第一列表示字段名称第二列Type则表示对应字段的字段类型。比如id字段是一个int类型。
第二种方式是使用SHOW CREATE TABLE 表名命令例如SHOW CREATE TABLE tb_rider得到下面的结果。
CREATE TABLE tb_rider (id int(11) NOT NULL AUTO_INCREMENT,name varchar(32) NOT NULL DEFAULT COMMENT 姓名,real_name_certify_state int(11) NOT NULL DEFAULT 0 COMMENT 身份证认证状态,is_deleted tinyint(4) NOT NULL DEFAULT 0 COMMENT 该用户是否还存在. 0: 不存在, 1: 存在,created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,level tinyint(4) NOT NULL DEFAULT 0 COMMENT 骑手等级0普通 1铜牌 2银牌 3金牌,level_city varchar(32) NOT NULL DEFAULT COMMENT 配送员等级城市,PRIMARY KEY (id),KEY ix_created_at (created_at),KEY ix_updated_at (updated_at)
) ENGINEInnoDB AUTO_INCREMENT9 DEFAULT CHARSETutf8 COMMENT配送员信息;
复制代码
我们以
name varchar(32) NOT NULL DEFAULT COMMENT 姓名
复制代码
来解释一下这里的语句。
name是字段名(列名)varchar表示字段类型为字符串NOT NULL表示这个字段不能为空, 为空的意思是没有指定任何值给这个字段(注意不等价于空字符串)DEFAULT 表示如果没有指定这个字段的值则使用空字符串作为默认值COMMENT 姓名是对这个字段的备注表示这个字段的业务含义只用做展示
4.2 索引
索引绝对算得上是关系型数据库中最关键同时也是最有难度的话题。即便是经验丰富的研发同学也经常会踩到索引的坑。不过我们这里介绍索引只是为了更好的服务于查询我会尽可能避免牵扯进一些复杂的概念和底层原理。
[1] 什么是索引
那么到底什么是索引呢你可以把数据库理解为一本很厚的书(假设有10万页)书中的内容就是数据库里的数据那么索引就是书的目录。 假设你从来没有阅读过这本书此刻你想要阅读书的第7章第2小节。如果没有目录你可能需要翻阅整本书找到你要阅读的内容。但是在有目录的情况下你就只需要先查一下目录找到对应的页码然后直接翻到那一页就能看到你想看的内容了。索引也是类似的首先查询索引找到目标数据的位置再从特定的位置读取出数据的内容。
如何设计索引是设计数据库表的时候考虑的关键点之一。索引一般由表中的某一列或者某几列构成一旦设置某一列为索引那么之后每次在往表中写入数据的时候都会更新这一列到索引中去。事实上索引在技术层面是比较复杂的涉及到磁盘I/O、B树、优化器(Optimizer)等很多技术概念不过我们先不去深究这些。
[2] 为什么索引很重要它有什么用
索引之所以重要最主要的原因是能够大大提高查询的速度。上面我们举了书的例子当这本书的页数足够大的时候(假设有2000万页)如果没有目录想要查阅其中的某一章节的内容那几乎就是天方夜谭了。数据库也是如此当表中的数据只有几行或者几十行、几百行的时候有没有索引其实差别不大但是当表中的数据非常非常多的时候(比如众包的运单表2000万 行)如果没有索引要找到某一条目标数据查询的速度就会非常非常非常的慢。
[3] 如何使用索引
要使用索引非常简单只需要在WHERE条件中使用到索引列作为查询条件让我们举个例子。
idorder_idorder_staterider_idrider_namemerchant_customer_distancecreated_at1300000201712300001401Stark2.52017-12-30 12:34:172300000201712300002401Stark1.82017-12-30 12:34:183300000201712300003402Banner1.82017-12-30 13:20:024300000201712300004405Natasha2.72017-12-30 13:34:195300000201712300005401Stark1.22017-12-30 16:01:036300000201712300006403Rogers0.52017-12-30 16:08:577300000201712310001206Barton1.32017-12-31 09:12:078300000201712310002807Coulson2.92017-12-31 09:10:339300000201712310003802Banner0.72017-12-31 09:18:1010300000201712310004203Rogers2.22017-12-31 10:34:01113000002017123100051000.32017-12-31 19:29:02123000002017123100061001.32017-12-31 19:29:27133000002017123100071003.02017-12-31 19:30:01
还是这张tb_order表假设这张数据表中order_id是索引列那么当我们以order_id作为查询条件时我们就利用了索引比如下面这条Sql。
SELECT * FROM tb_order WHERE order_id 300000201712310007;
复制代码
当然啦类似的使用order_id作为查询条件的Sql也都会利用到索引看看你是否都理解下面两条Sql语句的含义。
1. SELECT * FROM tb_orderWHERE order_id IN (300000201712310007, 300000201712310006)AND order_state 40;
2. SELECT order_id, order_state FROM tb_orderWHERE order_id 300000201712300001AND order_id 300000201712300006AND order_state 40;
复制代码
那么如果一张表里面不止一列是索引而在查询的Sql中这些索引列都作为了WHERE语句的查询条件会使用哪个列作为索引还是都使用假设tb_order表中order_id和rider_id两列都是索引列那么下面这条Sql语句会使用哪个作为索引呢
SELECT * FROM tb_order
WHERE order_id 300000201712310001
AND order_id 300000201712310007
AND rider_id 0;
复制代码
答案是不确定的。使用哪个索引甚至是否使用索引从根本上来说是由优化器(Optimizer)决定的它会分析多个索引的优劣以及使用索引和不使用索引的优劣然后选择最优的方式执行查询。这部分话题就太过复杂了这里不做展开。尽管有优化器(Optimizer)的存在但是对于我们的查询来说能够使用明确的索引字段作为查询条件的就应该尽可能使用索引字段。
[4] 索引的类型、如何确定表中的哪些列是索引列
还记得字段类型一节中提到的DESC 表名和SHOW CREATE TABLE 表名语法吗前面我们将这两个语法用在了tb_rider表上这一节让我们看一看tb_order表。
首先是DESC tb_order我们会得到下面的结果。
FieldTypeNullKeyDefaultExtraidbigint(20)NOPRInullauto_incrementorder_idbigint(20)NOUNI0rider_idint(11)NO0rider_namevarchar(100)NOorder_statetinyint(4)NO0is_deletedtinyint(4)NO0grabbed_timetimestampNOCURRENT_TIMESTAMPmerchant_customer_distancedecimal(10,2)NO0.00created_atdatetimeNOMULCURRENT_TIMESTAMPupdated_atdatetimeNOMULCURRENT_TIMESTAMPon update CURRENT_TIMESTAMP
之前我们关注的是Type这一项这里让我们关注Key这一项。我们看到有些列对应的Key是空的这就表示这一列(或者叫这个字段)不是索引列(或者叫索引字段)。但id、order_id、created_at和updated_at这几列对应的Key均是有值的这说明这几列都是索引列。但这几列Key的值又各不相同这是为啥呐这是以内索引也分为不同的类型让我们逐个来解释一下。
PRI是primary的缩写标记这一列为主键主键的概念我们在一开始的时候有介绍过就是用来唯一标识表中每一行数据的索引UNI: 是unique的缩写顾名思义就是唯一的意思, 被设置为UNI KEY的列不允许出现重复的值如果尝试向表中插入这一列的值完全相同的两行数据则会引发报错。我猜你肯定会觉得疑惑那UNI KEY和PRI KEY有啥区别首先是这两种类型的索引在实现上是有区别的(这一点咱们不深究涉及到了数据库底层对索引的实现)其次PRI KEY更多的是数据库层面的语义仅仅是描述数据的唯一性而UNI KEY则更多是业务层面的语义比如说这里的order_id字段因为业务上不能存在两个运单号完全相同的运单所以需要把order_id这一列设置为UNI KEYMUL是multiple的缩写表示这一列是被设置为一个普通索引。之所以叫做multiple是因为此时可能这一列单独作为索引也可能这一列和其他标记为MUL的列共同构成了一个索引(这种由多列共同构成的索引被叫作复合索引)
现在我们还处在Sql以及数据库知识(是的除了Sql我还偷偷介绍了一些数据库原理)学习的初级阶段所以让我们知道这写差异但是不着急去把这些搞得一清二楚它们都是索引只要合理使用都可以帮助我们加快Sql查询的效率。
另一种识别表中索引列的方法就是通过SHOW CREATE TABLE 表名命令比如SHOW CREATE TABLE tb_order我们得到下面的结果。
CREATE TABLE tb_order (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 对外不提供内部使用,order_id bigint(20) NOT NULL DEFAULT 0 COMMENT 运单的跟踪号可以对外提供,rider_id int(11) NOT NULL DEFAULT 0 COMMENT 配送员id,rider_name varchar(100) NOT NULL DEFAULT COMMENT 配送员名字,order_state tinyint(4) NOT NULL DEFAULT 0 COMMENT 配送状态,is_deleted tinyint(4) NOT NULL DEFAULT 0,grabbed_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 抢单时间,merchant_customer_distance decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 商铺到顾客步行距离,created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id),UNIQUE KEY uk_order_id (order_id),KEY ix_created_at (created_at),KEY ix_updated_at (updated_at)
) ENGINEInnoDB AUTO_INCREMENT14 DEFAULT CHARSETutf8 COMMENT配送单;
复制代码
看到末尾几行的PRIMARY KEY、UNIQUE KEY和KEY了吗它们就对应于DESC tb_order结果中的PRI、UNI和MUL分别标识主键索引、唯一索引和普通索引。每一行括号内的字段就表示对应的索引列。
4.3 JOIN语法家族
我尝试了好几种解释清楚JOIN语法的方法(JOIN语法的确有些复杂)始终不能让我自己满意最终决定还是从一个例子开始。让我们首先看一张新的表建表语句长下面这样。
CREATE TABLE tb_grab_order_limit (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 自增主键,rider_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT 骑手id,order_grab_limit INT(11) NOT NULL DEFAULT 0 COMMENT 接单上限,is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 该记录是否被删除,created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,PRIMARY KEY(id),KEY ix_rider_id (rider_id),KEY ix_created_at (created_at),KEY ix_updated_at (updated_at)
) ENGINE InnoDB DEFAULT CHARSETutf8 comment自定义骑手接单上限表;
复制代码
小温习
参考上面的建表语句尝试回答下面这几个问题。
这张表的表名是什么order_grab_limit这个字段的含义是什么这张表的主键索引是什么有几个唯一索引、几个普通索引
没错这就是自定义骑手接单上限表。描述了某一个骑手(rider_id)对应的他的接单上限(order_grab_limit)。表中的数据如下。
idrider_idorder_grab_limitis_deletedcreated_atupdated_at111102018-02-25 17:22:032018-02-25 17:22:0322902018-02-25 17:22:212018-02-25 17:22:2134902018-02-25 17:22:312018-02-25 17:22:3146702018-02-25 17:22:392018-02-25 17:22:39510802018-02-25 17:22:462018-02-25 17:22:46
再让我们回顾一下前面反复用到的tb_rider表。
idnamereal_name_certify_statelevellevel_cityis_deletedcreated_atupdated_at1Stark23102017-01-01 22:00:192018-01-01 06:40:012Banner23902017-04-28 12:01:192018-01-01 06:40:013Rogers22102017-04-10 17:24:012018-01-01 06:40:014Thor10102017-12-31 23:10:392018-01-01 06:40:015Natasha21102017-02-11 15:03:132018-01-01 06:40:016Barton21902017-02-11 15:04:192018-01-01 06:40:017Coulson23902017-01-03 23:00:222018-01-01 06:40:018Coulson10202017-01-05 10:10:232018-01-01 06:40:01
(终于铺垫完啦)
[1] 从LEFT JOIN开始
以这两张表为基础设想一个场景假设要查询tb_rider表中所有骑手对应的自定义接单上限。我们的Sql应该怎么写呢
**思路1**先查出tb_rider表中所有骑手id再根据这些骑手id作为查询条件通过前面学习过的IN语法从tb_grab_order_limit表中查询出所对应的自定义接单上限的记录。
SELECT id FROM tb_rider;
复制代码
和
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (1, 2, 3, 4, 5, 6, 7, 8);
复制代码
思路1显然是个Bad idea。但是思路1诠释了解决这个查询问题的基本要点。
我们最终想要的数据是需要结合tb_rider和tb_grab_order_limit两张表共同得出的关联这两张数据表的条件是骑手id因为查询的要求是tb_rider表中所有骑手因此应该以tb_rider表中的骑手id作为查询参考集合不是所有tb_rider表中的骑手都配置了自定义接单上限思路1的查询方案存在一个缺点就是我们需要根据查询结果在逻辑上做一个转换得知哪些骑手没有配置自定义接单上限( 不在返回结果中的骑手)
**思路2**基于这几个要点我们可以使用LEFT JOIN语法下面是对应的Sql语句。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
这里先介绍一下JOIN语法的基本结构表1 (INNER/LEFT/RIGHT/FULL) JOIN 表2 ON 表1.列1 表2.列2。JOIN关键字前后连接的是两张需要关联查询的数据表ON关键字后面跟着关联的条件。一共有四种类型的JOIN他们分别是INNER JOIN、LEFT JOIN、RIGHT JOIN和FULL JOIN。以例子中的LEFT JOIN为例表1 LEFT JOIN 表2 ON 表1.列1 表2.列2的含义是遍历表1中的列1的值如果表2中列2的值有和它相等的则展示对应的记录如果没有表2.列2和表1.列1相等则展示为null。
思路2的例子中tb_rider LEFT JOIN tb_grab_order_limit ON tb_rider.id tb_grab_order_limit.rider_id的含义是遍历tb_rider表中id这一列(tb_rider表的id字段业务含义就是骑手id)的值寻找tb_grab_order_limit表中rider_id列的值和它相等的记录如果不存在则是null。
我们还看到SELECT语句的内容和我们之前使用的很类似但又稍微有点不一样都是表名.列名的书写形式。其实这主要是指明了字段所属的表因为JOIN的两张数据表中可能存在的相同名称的列例如tb_rider表和tb_grab_order_limit表都有id字段但含义截然不同这样写更加明确。
最终思路2的结果如下。
idorder_grab_limit1112949677null8null5null3null
我们看到骑手id(7, 8, 5, 3)的几个骑手没有配置自定义的接单上限但因为是LEFT JOIN他们仍然会展示在查询结果中不过因为没有接单上限的记录order_grab_limit的结果为null。
让我们再回头看一下表名.列名这个写法。如果思路2中的Sql改成下面这样返回结果会变成什么呢
SELECT tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
让我们来分析一下。我们知道LEFT JOIN的返回结果集合是以它左侧连接的数据表决定的所以结果集仍然包含8条记录但是骑手id(7, 8, 5, 3)这个骑手没有对应的接单上限的配置因此当我们展示这几个骑手的tb_grab_order_limit.rider_id列的值的时候类似于tb_grab_order_limit.order_grab_limit也是null。因此结果是下面这样。
rider_idorder_grab_limit111294967nullnullnullnullnullnullnullnull
如果你还是不太明白然我们在SELECT的时候加上tb_rider.id或许有助于理解。
SELECT tb_rider.id, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
结果是。
idrider_idorder_grab_limit11112294496677nullnull8nullnull5nullnull3nullnull
[2] LEFT JOIN的姊妹篇RIGHT JOIN
前面我们知道LEFT JOIN是以连接的左侧表作为查询的结果集的依据RIGHT JOIN则是以连接的右侧表作为依据。让我们考虑另一个场景假设想要查询所有设置了自定义接单上限的骑手姓名。应该如何写这个Sql呢
先在聪明的大脑里思考几分钟。此时你需要类比LEFT JOIN需要理解上一段内容讲述的LEFT JOIN知识点可能需要回到上一段再看一看示例Sql语句以及对应的结果。没关系一开始学习的时候慢慢来。
答案是这样的。
SELECT tb_grab_order_limit.rider_id, tb_rider.name
FROM tb_rider RIGHT JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
对应的查询结果则是。
rider_idname1Stark2Banner4Thor6Barton10null
如果这个结果和你脑海中思考的结果不一样不要着急让我们再来解释一下。RIGHT JOIN是以连接的右侧表为依据而tb_grab_order_limit中的骑手id(1, 2, 4, 6, 10)其中骑手id为10的骑手在tb_rider表中是没有的所以name为null。
小测验
尝试下将上面的这条Sql语句改写成LEFT JOIN吧(要求得到相同的查询结果)
[3] 一丝不苟的INNER JOIN
之所以叫“一丝不苟”的INNER JOIN是因为INNER JOIN是非常严格的关联查询换句话说必须是根据JOIN条件两张表中存在匹配记录的才作为结果集返回。让我们回顾下[1]中LEFT JOIN的Sql。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
它的返回结果是。
idorder_grab_limit1112949677null8null5null3null
如果我们将LEFT JOIN改为INNER JOIN呐修改后的Sql像这样。
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_rider INNER JOIN tb_grab_order_limit
ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
这时返回的查询结果变成了。
idorder_grab_limit111294967
这是因为INNER JOIN会遍历连接一侧的表根据ON后的连接条件和连接另一侧的表进行比较只有两张表中存在匹配的记录才会作为结果集返回。例如这里它会遍历tb_rider表中id字段的值并且去tb_grab_order_limit表中寻找rider_id与之匹配的记录如果找到则作为结果返回。 B.T.W INNER JOIN 和 JOIN是等价的换句话说表1 INNER JOIN 表2 ON...和表1 JOIN 表2 ON...是完全等价的。 小测验
猜测一下下面的这条Sql语句的返回结果是什么
SELECT tb_rider.id, tb_grab_order_limit.order_grab_limit
FROM tb_grab_order_limit INNER JOIN tb_rider
ON tb_grab_order_limit.rider_id tb_rider.id;
复制代码
提示这里交换了一下INNER JOIN连接的两张表的位置根据INNER JOIN的特性查询结果会有影响嘛
[4] 心大的FULL JOIN
FULL JOIN其实并不在乎匹配与否而是将连接的两张表中所有的行都返回如果有匹配的则返回匹配的结果如果没有匹配则哪张表中缺失则对应的将当前这条记录标记为null。看一个例子就明白啦
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider FULL JOIN tb_grab_order_limit ON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
这条Sql语句的查询结果是这样的。
idnamerider_idorder_grab_limit1Stark1112Banner294Thor496Barton673Rogersnullnull5Natashanullnull7Coulsonnullnull8Coulsonnullnullnullnull1010
可以看到tb_rider表中骑手id(3, 5, 7, 8)的骑手在tb_grab_order_limit表中没有匹配的记录而tb_grab_order_limit表中骑手id(10)的骑手在tb_rider表中没有匹配记录但是它们都作为结果集返回了。只不过缺失tb_grab_order_limit记录的rider_id和order_grab_limit字段值为null而缺失tb_rider记录的id和name字段的值为null。
事实上绝大多数情况下FULL JOIN都不会被用到。而且在一些数据库管理系统中例如MySql(我们的线上环境主要使用的就是MySql)是不支持FULL JOIN语法的。对于上面的查询语句需要使用一些技巧通过LEFT JOIN、RIGHT JOIN以及UNION(这篇教程中我们不讨论UNION语法哦)语法的组合来实现同样效果的查询。
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.order_grab_limit
FROM tb_rider LEFT JOIN tb_grab_order_limit ON tb_rider.id tb_grab_order_limit.rider_id
UNION
SELECT tb_rider.id, tb_rider.name, tb_grab_order_limit.rider_id, tb_grab_order_limit.rider_id
FROM tb_rider RIGHT JOIN tb_grab_order_limit ON tb_rider.id tb_grab_order_limit.rider_id
WHERE tb_rider.id IS null;
复制代码
这已经超出了这篇教程的讨论范围啦如果想要挑战一下自己以下是一些提示。
UNION连接两条SELECT语句作用是将两个SELECT语句的查询结果取交集第2条SELECT语句中的WHERE tb_rider.id IS null 是为了对存在匹配的数据记录去重(否则UNION之后会有重复的结果)WHERE语句是在RIGHT JOIN之后UNION之前执行的
试着在这两条提示下理解一下这条Sql语句如果能够弄明白这条语句是如何等价于FULL JOIN的那么说明你对JOIN家族的语法已经基本掌握啦。如果暂时还不能弄得非常明白也没关系多看一看例子多写一写实践一下慢慢就会明白啦。
题外话
从上面的讲解我们了解到JOIN的四种用法总结一下。
INNER JOIN关键字在两张表中都有匹配的值的时候返回匹配的行LEFT JOIN关键字从左表返回所有的行即使在右表中没有匹配的行RIGHT JOIN关键字从右表返回所有的行即使在左表中没有匹配的行FULL JOIN关键字从左表和右表那里返回所有行即使右表的行在左表中没有匹配或者左表的行在右表中没有匹配这些行也会返回
不过这些都是刻板的文字总结让我们换个视角总结一下这集中JOIN语法。
离散数学中在讨论集合论的时候介绍过**“韦恩图”**的概念它清楚的描述了数据集合之间的关系。而JOIN的这4种操作也正好对应了4种集合运算下面的这张图(Figure 1)很清楚的描述了这种关系。 4.4 嵌套的SELECT语法
再来看一下讲述LEFT JOIN的开始我们提到的那个例子查询tb_rider表中所有骑手对应的自定义接单上限。当时我们首先提出了思路1是分为2个步骤的。
SELECT id FROM tb_rider;
复制代码
和
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (1, 2, 3, 4, 5, 6, 7, 8);
复制代码
我们说这个思路不好这是显然的因为在现实场景中往往数据集合都很大(例如这里的rider_id在现实中可能是成百上千甚至成千上万个)思路本身没有问题但无法操作执行。所以在4.3节我们选择通过JOIN语法来实现同样的查询。那是不是思路1就真的只能是个纸上谈兵的思路了呢当然不是啦我们还可以使用嵌套的SELECT语句就像这样。
SELECT rider_id, order_grab_limit FROM tb_grab_order_limit
WHERE rider_id IN (SELECT id FROM tb_rider);
复制代码
这个写法非常好理解WHERE rider_id IN (SELECT id FROM tb_rider)首先执行括号中的语句SELECT id FROM tb_rider然后执行IN筛选就是我们的思路1描述的那样。于是得到下面的结果。
rider_idorder_grab_limit111294967
复习题
回想一下上面的结果和以下哪条Sql语句的执行结果是一致的呢为什么是一致的为什么和其他的不一致
1. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limitFROM tb_rider LEFT JOIN tb_grab_order_limitON tb_rider.id tb_grab_order_limit.rider_id;
2. SELECT tb_grab_order_limit.rider_id, tb_rider.nameFROM tb_rider RIGHT JOIN tb_grab_order_limitON tb_rider.id tb_grab_order_limit.rider_id;
3. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limitFROM tb_rider INNER JOIN tb_grab_order_limitON tb_rider.id tb_grab_order_limit.rider_id;
4. SELECT tb_rider.id, tb_grab_order_limit.order_grab_limitFROM tb_rider FULL JOIN tb_grab_order_limitON tb_rider.id tb_grab_order_limit.rider_id;
复制代码
小测验
思考一下以下这个场景看看能否写出它对应的Sql语句
场景筛选出所有通过实名认证(real_name_certify_state2)的金牌(level3)骑手(tb_rider表)在2017-12-30当天(created_at xxx AND created_at yyy)所跑运单(tb_order表)的运单号(order_id)。
想一想有几种写法呢
5 闯关答题快速复习
前面的几个段落我们学习了Sql查询中最常用而且特别好用的语法知识让我们简单总结一下。
数据库、数据表的概念最基本的Sql查询结构IN查询和逻辑操作语法(AND/OR)对查询结果进行排序和LIMIT语法聚合函数(COUNT/AVG/SUM)和DISTINCT语法对查询结果分组(GROUP BY)对聚合函数的结果进行筛选的HAVING语法字段类型和索引的概念和作用JOIN语法的一家子(LEFT JOIN/RIGHT JOIN/INNER JOIN/FULL JOIN)嵌套的SELECT语法
学习了这么多知识点实在是太腻害了给自己点赞
但是(凡事都有个但是)...
想要把这些知识点融会贯通灵活应用到现实工作中更多变、更复杂的查询场景仅仅是“学会”是不够的还需要更多的“练习”和“回味”。
这个部分我设计了一个“闯关答题”项目通过思考和回答这些闯关题帮助你更好的掌握上面提到的知识点。
先来看一下答题将要用到的数据表。
[1] 商品数据表tb_product
idproduct_idnameprice11001iPad Pro 10.5 64G WLAN488821002Macbook Pro 2017 13.3 i5/8G/256GB1388831003iPhone X 64G8388
建表语句
CREATE TABLE tb_product (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 自增主键,product_id bigint(20) NOT NULL DEFAULT 0 COMMENT 商品id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,price int(11) NOT NULL DEFAULT 0 COMMENT 商品价格,PRIMARY KEY (id),UNIQUE KEY uk_product_id (product_id)
) ENGINEInnoDB AUTO_INCREMENT14 DEFAULT CHARSETutf8 COMMENT商品信息表;
复制代码
字段含义
id自增主键product_id商品idname商品名称price商品单价单位是元
[2] 用户数据表tb_customer
idcustomer_idnamegenderbalance1NO100001火火女188882NO100002拨泼抹女90003NO100003艾桥男79904NO100004水娃女8388
建表语句
CREATE TABLE tb_customer (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 自增主键,customer_id varchar(100) NOT NULL DEFAULT COMMENT 用户id,name varchar(100) NOT NULL DEFAULT COMMENT 用户姓名,gender varchar(30) NOT NULL DEFAULT COMMENT 用户性别,balance int(11) NOT NULL DEFAULT 0 COMMENT 账户余额,PRIMARY KEY (id),UNIQUE KEY uk_customer_id (customer_id)
) ENGINEInnoDB AUTO_INCREMENT14 DEFAULT CHARSETutf8 COMMENT用户信息表;
复制代码
字段含义
id自增主键customer_id用户idname用户姓名gender用户的性别balance用户当前的可用账户余额单位是元
[3] 订单数据表tb_order
idorder_idcustomer_idproduct_idquantity1NUM1000301NO100001100112NUM1000302NO100001100223NUM1000303NO100002100224NUM1000304NO100003100215NUM1000305NO10000110031
建表语句
CREATE TABLE tb_order (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 自增主键,order_id varchar(100) NOT NULL DEFAULT COMMENT 订单id,customer_id varchar(100) NOT NULL DEFAULT 0 COMMENT 用户id,product_id bigint(20) NOT NULL DEFAULT 0 COMMENT 商品id,quantity int(11) NOT NULL DEFAULT 0 COMMENT 商品价格,PRIMARY KEY (id),UNIQUE KEY uk_order_id (order_id)
) ENGINEInnoDB AUTO_INCREMENT14 DEFAULT CHARSETutf8 COMMENT订单数据表;
复制代码
字段含义
id自增主键order_id订单号customer_id下单用户idproduct_id购买的商品idquantity购买的数量
了解完需要用到表结构我们就要开始答题啦
第一关查询账户余额大于1万元的用户id和姓名
Answer:
SELECT customer_id, name FROM tb_customer WHERE balance 10000;
复制代码
customer_idnameNO100001火火
第二关查询账户余额小于1万元且性别为女生的用户姓名
Answer:
SELECT name FROM tb_customer WHERE balance 10000 AND gender女;
复制代码
name拨泼抹水娃
第三关查询用户id为NO100001和NO100002的用户所有购买记录的订单号
HintIN
Answer:
SELECT order_id FROM tb_order WHERE customer_id IN (NO100001, NO100002);
复制代码
order_idNUM1000301NUM1000302NUM1000303NUM1000305
第四关查询用户id为NO100001、NO100002两位用户所有的购买记录(所有字段)要求按照优先以商品id递增、其次以订单号递减的规则展示数据
HintIN、ORDER BY
Answer:
SELECT * FROM tb_order WHERE customer_id IN (NO100001, NO100002)
ORDER BY product_id ASC, order_id DESC;
复制代码
idorder_idcustomer_idproduct_idquantity1NUM1000301NO100001100113NUM1000303NO100002100222NUM1000302NO100001100225NUM1000305NO10000110031
第五关查询性别为女生的用户总数
HintCOUNT
Answer:
SELECT COUNT(customer_id) FROM tb_customer WHERE gender女;
复制代码
COUNT(customer_id)3
第六关查询NO100001、NO100002、NO100003三位用户各自购买商品的总数(不区分商品类型)输出购买商品件数大于等于2件的用户id以及他们对应购买的商品总数
Warning“购买商品的总数”和上一关“女生用户的总数”这两个**“总数”**一样吗
HintIN、SUM、HAVING
Answer:
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (NO100001, NO100002, NO100003)
GROUP BY customer_id
HAVING SUM(quantity) 2;
复制代码
customer_idSUM(quantity)NO1000014NO1000022
第七关查询NO100001、NO100002、NO100003三位用户各自购买商品的总数(不区分商品类型)输出购买总数前两名的用户id以及他们对应购买的商品总数
HintIN、SUM、ORDER BY、LIMIT
Answer:
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (NO100001, NO100002, NO100003)
GROUP BY customer_id
ORDER BY SUM(quantity) DESC
LIMIT 2;
复制代码
customer_idSUM(quantity)NO1000014NO1000022
第八关查询所有用户各自购买商品的总数(不区分商品类型)输出购买商品件数大于等于2件的用户id以及他们对应购买的商品总数要求给出至少两种写法。
Warning注意是“所有用户”不是所有的用户都购买了商品
Hint关联查询有哪些方法
Answer:
写法一嵌套的SELECT
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (SELECT customer_id FROM tb_customer)
GROUP BY customer_id
HAVING SUM(quantity) 2;
复制代码
customer_idSUM(quantity)NO1000014NO1000022
写法二使用LEFT JOIN语法
SELECT tb_customer.customer_id, SUM(tb_order.quantity) FROM tb_customer
LEFT JOIN tb_order ON tb_customer.customer_id tb_order.customer_id
GROUP BY tb_customer.customer_id
HAVING SUM(tb_order.quantity) 2;
复制代码
customer_idSUM(tb_order.quantity)NO1000014NO1000022
第九关查询所有用户各自购买商品的总数(不区分商品类型)输出购买总数前两名的用户id以及他们对应购买的商品总数要求给出至少两种写法。
Hint关联查询有哪些方法
Answer:
写法一嵌套的SELECT
SELECT customer_id, SUM(quantity) FROM tb_order
WHERE customer_id IN (SELECT customer_id FROM tb_customer)
GROUP BY customer_id
ORDER BY SUM(quantity) DESC
LIMIT 2;
复制代码
customer_idSUM(quantity)NO1000014NO1000022
写法二使用LEFT JOIN语法
SELECT tb_customer.customer_id, SUM(tb_order.quantity) FROM tb_customer
LEFT JOIN tb_order ON tb_customer.customer_id tb_order.customer_id
GROUP BY tb_customer.customer_id
ORDER BY SUM(tb_order.quantity) DESC
LIMIT 2;
复制代码
customer_idSUM(tb_order.quantity)NO1000014NO1000022
第十关以下哪几条Sql语句使用到了索引分别是哪些字段上的索引是什么类型的索引
1. SELECT name FROM tb_customer WHERE customer_id 1001;
2. SELECT product_id, name FROM tb_product WHERE price 5000;
3. SELECT order_id, customer_id, product_id FROM tb_orderWHERE order_id NUM1000302 AND customer_id NO100001AND product_id 1002;
4. SELECT order_id FROM tb_order WHERE id 2;
复制代码
Hint索引
Answer:
sql序号是否使用到索引索引所在字段索引类型1是customer_idUNIQUE KEY2否--3是order_idUNIQUE KEY4是idPRIMARY KEY 阅读博客还不过瘾 欢迎大家扫二维码通过添加群助手加入交流群讨论和博客有关的技术问题还可以和博主有更多互动 博客转载、线下活动及合作等问题请邮件至 shadowfly_zylhotmail.com 进行沟通
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/910358.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!