select * from a where b >0 and c = '2019-01-01' group by d having e>0 order by m;
            
        
提醒:《极简SQL》的初衷是降低SQL的认知负担。基于这个目的,所以本系列内容不是挖掘源码也不是底层面的分析,而是着眼于SQL中的名词概念术语。把这些东西说透,走进读者的内心,人们才能真正的喜欢SQL。既然我具备这种能力(从普通的概念中挖掘出新意),那么让我去分析源码研究底层次的原理,我同样可以写的比某些数据库专家写的更有灵性。不过,我不认可钻入SQL的底层去研究源码,因为我觉得这是一种错误并误人子弟的学习方式。最后强调一点,由于上线紧迫,部分图片摘录于互联网上,我承诺他日之后必定替换成原创图片。
《极简SQL》
前言
第一节:行列和分组之别:看见与看错
第二节:分组之入门
第三节:行列的存储
第四节:B 树 和 B+ 树
第五节:InnoDB 传奇故事
第六节:Case的用法
第七节:时间杂谈
第八节:行锁和表锁
第九节:表连接
第十节:第十节:utf8 和 utf8mb4 的区别
第十一节:SQL SELECT DISTINCT 语句
第十二节:SQL CONTACT() GROUP_CONCAT() 函数介绍
思考题
前言

《极简SQL》脱胎于《深度学习SQL》。

首先简单的说一下《深度学习SQL》,顾名思义,《深度学习SQL》重在于对SQL的深度理解。

此时出现一个问题,怎么算是深度学习呢?是从源码级别分析SQL的运行原理吗?

有人就是这么干的。掘金网站上有个人,裸辞之后专攻MySQL源码的学习。

而我对此做法不太认可(具体的缘由就不再阐述了),所以我编写的《深度学习SQL》不是从源码级别进行分析,而是从概念层面进行深入的分析的。

什么是"从概念层面进行深入的分析"呢?

比方说,我会分析行列和分组的区别,这样大家就能对分组的理解更到位,进而对having,聚合函数等相关的概念也更深入人心。

再如,我会通过Java中case的用法与SQL中case的用法相互对照,这样大家在用到Java case的时候,自然又复习了一遍SQL case,从而能将SQL中的CASE运用自如。

可以说,这些分析的过程都是一些不起眼的联想和想象,甚至透漏着“小聪明”,但是却是将外界刻板的死知识内化成切身体会的最佳学习方法。

很多时候,我们看到别人的答案之后恍然大悟,而自己却憋上半天也无能无力呢?因为对我们来说,这些知识是死的,没有内化成自己的能力。

接着说一下《极简SQL》,希望让大家理解起来极度省事,能力提升的极度迅速。花费3天的学习,顶上你自己学习三年的时间。

当然,这种说法略显夸张。不过细想一下也不夸张。很多人接触SQL有三、五年,甚至十年了,但是并没有真正的去体会一下SQL里面的概念。我希望通过《极简SQL》的学习,让这类人能迅速的体会SQL的深厚,从而爱上SQL。

第一节:行列和分组之别:看见与看错

如下图所示,你能说出这个表是由行组成还是由分组组成的吗?



估计很多人看到这个问题会一脸茫然,心想着:怎么还会有行和分组之说呢?

大多数人想当然的会认为这些都是数据的行记录而已。请细想一下,我们通常查询出来的结果集(数据表),有可能是一行一行的真实的数据记录,还有可能是若干个行聚合到一起形成的分组。

行和分组的展示效果,对我们而言,都是一样的。但是,我们从内心深处要有行和分组的区别,这对我们灵活的使用SQL很有帮助。我认为,知识写在书本上对于大家都是一样的,所不同的是每个人的悟性各异,思考出来的东西也不一样,多挖掘多琢磨,看到不一样的东西对技术的提升非常帮助。

看得见的行和列

在数据库里面,一行数据和一列数据都能被人所感知,并且一行记录往往还带有一个主键,而一列数据,不仅有列名,还有列的类型。

看错的分组

人人常常将分组当做行,但是它并不是行。行是物理存在的,而分组却是逻辑存在的。对于由行组成的数据集,可以通过where来过滤,但是对于分组的数据集,只能通过having来过滤。分组,一个很重要的概念,后面将是一个单独的知识点,敬请查看下文。

附录:上图的SQL语句

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_employee
-- ----------------------------
DROP TABLE IF EXISTS `t_employee`;
CREATE TABLE `t_employee`  (
  `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `salary` decimal(10, 2) NULL DEFAULT NULL,
  `address` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `department` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_employee
-- ----------------------------
INSERT INTO `t_employee` VALUES ('DEV001', 'Tom', 25, 5000.00, '深圳', '研发部');
INSERT INTO `t_employee` VALUES ('DEV002', 'Adam', 25, 5000.00, '深圳', '研发部');
INSERT INTO `t_employee` VALUES ('HR001', 'Brad', 30, 9000.00, '北京', '人力资源部');
INSERT INTO `t_employee` VALUES ('HR002', 'Brant', 30, 9000.00, '北京', '人力资源部');
INSERT INTO `t_employee` VALUES ('HR003', 'Brown', 35, 4000.00, '杭州', '技术支持部');
INSERT INTO `t_employee` VALUES ('SALE001', 'Bill', 27, 8000.00, '上海', '销售部');
INSERT INTO `t_employee` VALUES ('SALE002', 'Billy', 27, 8000.00, '上海', '销售部');
INSERT INTO `t_employee` VALUES ('SALE003', 'Bob', 27, 8000.00, '上海', '销售部');

SET FOREIGN_KEY_CHECKS = 1;

第二节:分组之入门
1、数据分组入门

数据分组用来将数据分为多个逻辑组,从而可以对每个组进行聚合运算。SQL语句中使用GROUP BY子句进行分组,使用方式为:GROUP BY 分组字段1,分组字段2... 。

分组语句常常和聚合函数一起使用,GROUP BY子句负责将数据分成逻辑组,而聚函数则对每一个组进行统计计算。

虽然GROUP BY子句常常和聚合函数起使用,不过GROUP BY子句并不是离开聚合函数而无法单独生存。虽然不使用聚合函数的GROUP BY子句看起来影响不大,但它能够帮助我们更好地理解数据分组的原理。


select age from t_employee group by age;
	


这个SQL语句处理表中的所有记录,并且将age相同的数据行放到一组,分组后的数据可以看做一个临时的结果集,而select age语句则取出每组的age字段的值,这样我们就得到上面的员工年龄段表了。

上图所示,看似是行,其实是逻辑分组,虽然两者在外观显示上是一致的,但是要特别注意两者的区别。数据分组有以下三大特点。

数据分组特点一:分组与聚合函数

要分组的所有列都必须位于 GROUP BY子句的列名列表中,也就是没有出现在GROUP BY子句中的列(聚合函数除外)是不能放到SELECET语句后的列名列表中的。比如下面的SQL语句是错误的:


SELECT age, salary FROM t_employee GROUP BY age;

道理非常简单,采用分组以后的查询结果集是以分组形式提供的,由于每组中人员的工资都不样,所以就不存在能够统代表本组工资水平的salary字段了,所以上面的SQL语句是错误的。不过每组中员工的平均工资却能够统一代表本组工水平,所以可以对Salary使用聚合函数,下面的SQL语句则是正确的:


SELECT age, AVG(salary) FROM t_Employee GROUP BY age;

数据分组特点二:多级数据分组

GROUP BY子句中可以指定多个列,只需要将多个列的列名用逗号隔开即。指定多个分组规则以后,数据库系统将按照定义的分组顺序来对数据进行逐分组。首先按照第一个分组列进行分组,然后在每个小组内按照第一个分组列行再次分组...逐层分组,从而实现“组中组”的效果,而查询的结果集是以末一级分组来进行输出的。

比如下面的SQL语句将会列出所有分公司的所有门的情况:


SELECT address, department FROM t_employee GROUP BY address, department;

执行完毕我们就能看到下面的执行结果:



数据分组特点三:数据分组过滤使用HAVING语句

有的时候需要对部分分组进行过滤,比如只检索人数大于1的年龄段,有的开发人员会使用下面的SQL语句:


SELECT age,count(*) FROM t_employee GROUP BY age where count(*) > 1 ;

在数据库系统中执行上面的SQL语句时,数据库系统会提示语法错误。这是因为聚合函数不能在WHERE语句中使用,必须使用HAVING子句来代替,比如:


SELECT age,count(*) FROM t_employee GROUP BY age having count(*) > 1 ;

第三节:行列的物理存储

表是有行列组成的,这些行列是如何存储的呢?这是本节所关注的问题。

1、行列的存储(无索引)

如果一张表没有索引,那么一行记录就是一个节点,从而组成链表。

提醒:以上只是一种理论分析,在实际生活中,不存在没有索引的表。以MySQL为例,如果没有显式指定索引的话,MySQL 会尝试自动选择一个可以唯一标识数据的列作为主键,如果无法找到,则会生成一个隐含字段作为主键索引。
2、行列的存储(有主键索引)

如果建表的时候指定了主键索引,那么行列数据是以索引为节点构建一个B+树,从而可以高效的查找和增删数据。

先来建一个表和初始化一些数据,如下所示:


  DROP TABLE IF EXISTS `staff`;
  CREATE TABLE `staff` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `city` varchar(20) DEFAULT NULL,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` varchar(1)  DEFAULT NULL,
  `idcard` varchar(18) DEFAULT NULL,
  `brithday` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_idcard` (`idcard`),
  KEY `index` (`city`,`name`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

INSERT INTO `staff` VALUES (1,'北京', '李一', 21, '女', '443332224440711', '2009-5-20');
INSERT INTO `staff` VALUES (2, '北京', '李二', 22, '男', '443332224440712', '2019-5-7');
INSERT INTO `staff` VALUES (3,'深圳',  '李三', 33, '女', '443332224440713', '2019-5-6');
INSERT INTO `staff` VALUES (4,'广州', '王一', 21, '男', '443332224440714', '2019-4-3');
INSERT INTO `staff` VALUES (6, '北京', '王三', 36, '男', '443332224440716', '2019-3-27');
INSERT INTO `staff` VALUES (7, '北京', '张一', 47, '女', '443332224440717', '2018-2-14');
INSERT INTO `staff` VALUES (8, '上海','张二', 43, '男', '443332224440718', '2019-2-12');
INSERT INTO `staff` VALUES (9, '上海', '张三', 22, '男', '443332224440719', '2019-1-23');
INSERT INTO `staff` VALUES (11,'广州', '赵二', 55, '女', '443332224440721', '2018-9-4');
INSERT INTO `staff` VALUES (12, '广州', '赵三', 33, '女', '443332224440722', '2018-9-5');

InnoDB存储引擎是使用了B+树索引模型,所有数据都是存在在B+树中。



3、行列的存储(有主键索引又有其他索引)

如果一个表已有主键索引,后来又新建了其他的索引,这个时候,会以此索引为节点再构建一个B+树。

提醒:多一个索引就多一个B+树,与主键索引B+树不同的是,此类B+树的叶子节点存储的是主键索引,通过回表到主键索引B+树,从而得到完整的数据。

新建一个联合索引(`name`,`age`),其存储结构为:



从图中可以看到主键索引(`id`)叶子节点存储的是id字段和整行数据,而联合索引(`city`,`name`)存储的是city,name字段和主键的值。

下面再来看看一条语句


SELECT * from staff where city='上海';

这条语句会用到联合索引(`city`,`name`),它的执行流程是这样的

(1)在联合索引上找到(上海,张三)这条记录,取得id=9;

(2)再去主键索引上找到id=9的数据,取得row9整行数据

(3)在联合索引上找到(上海,张二)这条记录,取得id=8;

(4)再去主键索引上找到id=8的数据,取得row8整行数据;

这里可以看到非主键索引的查找过程,会多一步到主键索引上搜索的操作,这个操作称为回表。

刚才说到多了一步回表会带来额外消耗,大多数情况下我们都不会用主键作为查询条件,那么好像回表操作无可避免。如果再细心观察一下上面联合索引图例,我们发现(`city`,`name`)已经在索引树上,所以看看下面两条语句


SELECT city,name from staff where city='上海';

SELECT * from staff where city='上海';

语句1执行后,在联合索引(city,name)上可以拿到city、name两个字段的信息,不需要回表了。

语句2执行后,在联合索引(city,name)上可以拿不到所需要的信息,所以要回到主键树上拿数据。

像语句1这种直接可以在索引上就拿到所有信息的情况,也就是说索引覆盖了要查询的字段,我们称这种索引为覆盖索引。灵活使用覆盖索引,是提升性能的重要手段。

提醒:天底下只有一种索引:主键索引和非主键索引。只不过根据不同的场景,又给索引起了不同的名字,如上所示,有覆盖就叫覆盖索引,没有覆盖则称不上覆盖索引。
第四节:B 树 和 B+ 树

犹如C语言和C++语言一样,B+ 树是对B 树进行了扩展。

B 树 是一颗非常普通的树,如下所示:



正因为 B 树 是一颗非常普通的树,普通的一点特色也没有,所以我们搞不清楚它。但是B 树是有缺点的,因为每个节点都有数据,所以导致加载到内存的时候,内存存不下多少节点。

后来,诞生了 B+ 树,节点不再存储数据,只存储索引列,而在叶子节点存储整个行的数据。

因为只在叶子节点存储行数据,所以 B+ 树,也就没有传统意义上的父子节点那样明确的上下关系,如下图所示:



如上图所示,索引值15在每个节点都会出现,已经不再有传统意义上的父子节点。

但是一般数据库的索引都对B+树进行了优化,加了顺序访问的指针,这样在查找范围的时候,就很方便,比如查找15~50之间的数据:



第五节:InnoDB 传奇故事

很久之前,在芬兰有一个研发数据库的公司,名叫Innobase。公司开发了一款数据库产品InnoDB。这是一个完整的数据库,功能非常的完备。开发出来之后,创始人是想将这个数据库卖掉的,但是没有找到买家。

后来,另外一个芬兰技术大牛,跑到瑞典创立了数据库公司MySQL。MySQL的理念是开源和共享。MySQL 从设计之初,存储引擎就是可插拔的,允许公司或者个人按照自己的需求定义自己的存储引擎。

MySQL推出后,这种可插拔的存储引擎吸引了 Innobase公司创始人的注意,在和 MySQL 沟通之后,他决定将 InnoDB 作为一个存储引擎引入到 MySQL 中,MySQL 虽然支持 InnoDB ,但是实际上还是主推自家的 MyISAM。

但是 InnoDB 实在太优秀了,引起了Oracle 的注意,最终在 2006 年的时侯,后者大手一挥,就把 InnoDB 收购了。 MySQL 主推自家的 MyISAM ,日子过得很惨淡,最终在 2008 年被 Sun 公司以 10 亿美元拿下,这番收购巩固了 Sun 在开源领域的领袖的地位,可是一直以来 Sun 公司的变现能力都比较弱,最终 Sun 自己在 2009 年被 Oracle 收入囊中。

Oracle 收购 Sun 之后,InnoDB 和 MySQL 就都成了 Oracle 的产品了,这下整合就变得非常容易了,在后来发布的版本中,InnoDB 慢慢就成为了 MySQL 的默认存储引擎。

MySQL的亲儿子是MyISAM,从名字上就可以看出血缘关系。但是亲儿子到底不中用,没有养子InnoDB更有能耐,最终继承MySQL衣钵的是InnoDB。

第六节:Case的用法
Case的用法1

case函数的语法如下:


CASE expression
WHEN key1 THEN value1
WHEN key2 THEN value2
WHEN key3 THEN value3
ELSE defaultvalue
END

CASE函数对表达式 expression 进行测试,如果 expression 等于key1 则返回value1;如果expression等于key2则返回value2,如果expression等于key3则返回value3....以此类推,如果不符合所有的WHEN条件,则返回默认值defaultvalue。

CASE函数和普通编程语言中的SWITCH-CASE 语句非常类似。使用CASE函数可以实现非常复杂的业务逻辑。如下所示:


select name,
(
case name 
when 'Adam' then 'A'
when 'Tom' then 'T'
else 'B'
end 
) as level
from `t_employee`

Case的用法2

上中介绍的CASE语句的用法只能用来实现简单的“等于”逻辑。如果要实现“如果年龄小于18则返回未成年人,否则返回成年人”的功能是不可能的。值得庆幸的是,CASE函数还提供了第二种用法,其语法如下:


CASE
WHEN condition1 THEN returnvalue1
WHEN condition2 THEN returnvalue2
WHEN condition3 THEN returnvalue3
ELSE defaultreturnvalue
END

其中的condition1、condition 2、condition3为条件表达式,CASE 函数对这个表达式从前向后进行测试,如果条件condition1为真则返回returnvalue1,否则如果条件condition2为真则返回returnvalue2。否则如果条件condition3为真则返回returnvalue3。以此类推,如果不符合所有的WHEN条件,则返回默认值。


select name,
(
case
when salary < 5000 then 'low'
when salary <= 8000 then 'middle'
else 'heigh'
end 
) as salary
from `t_employee`

Java中case用法
switch case 语句语法格式如下:

switch(expression)
{ 
	case value : 
	//语句 
	break; //可选 
	case value : 
	//语句 
	break; //可选 
	//你可以有任意数量的case语句 
	default : //可选 
	//语句 
}


public class Test
{
	public static void main(String args[])
	{

		char grade = 'A';

		switch (grade)
		{
			case 'A':
				System.out.println("优秀");
				break;
			case 'B':
			case 'C':
				System.out.println("良好");
				break;
			case 'D':
				System.out.println("及格");
				break;
			case 'F':
				System.out.println("你需要再努力努力");
				break;
			default:
				System.out.println("未知等级");
		}
		System.out.println("你的等级是 " + grade);
	}
}

如何记住SQL中的case的语法格式

在SQL中case的用处,人人都能说出一二三来,在Java中case的用法,同样的人人皆知。但是在SQL中,能熟练的使用case,往往人不多了。究其原因,在于case的语法格式很多人没有记住。可以结合Java中case的用法来记忆:

第一:SQL中的CASE expression,相当于Java中的switch(expression)

第二: SQL中的WHEN key THEN value,相当于Java中的case value : statement

第三:SQL中的ELSE defaultvalue,相当于Java中的default : statement

第七节:时间杂谈
藕断丝连的两个近亲:字符串类和日期时间类型

信息系统中经常需要处理一些与日期、时间相关的数据,比如创建时间、更新时间等。我们可以使用字符串来保存这些数据,比如2019-08-08,但是使用字符串表示日期很难保证数据的正确性,而且进行数据检索的时候也会非常麻烦和低效,为此数据库系统提供的日期时间类型数据。MySQL有5种表示时间值的日期和时间类型,分别为:DATE,TIME,YEAR,DATETIME,TIMESTAMP。

在MySQL中,可以使用字符串来表示日期时间类型,数据库系统自动在内部将它们转换为日期时间类型。为什么可以这样呢?见下面的分析。

虽然字符串类型与日期时间类型属于完全不同的数据类型,但是毕竟日期时间类型是从字符串类型延伸出来的,故两者还是有一定的联系的:日期时间类型的数据仍然可以使用单引号或者双引号来表示,只是必须遵守某种格式罢了,如上表所示。所以说,两者是藕断丝连的。

时区的理解

时区困扰着我们每一个开发,只要你明白它的由来,那你就不再怨天尤人了,反而能更好的接收它。

人是自然界的动物,人的思维模式遵循着动物的本性和感性。从感性的角度而言,人只能感知三种情况:太阳升起了,太阳正头顶,太阳落下去三种场景。在白天的整个时段中,人们要明白太阳在正头顶的时候,能提醒自己白天已经过了一半,这样才不违背人的认知习惯。 时区的划分,就是为了照顾人的这种感性认识,否则全世界共用一个时区,那么太阳整头顶的时候,有的地方是7点,而有的地方是19点,那人们岂不是疯掉了,这完全违背了人的主观认识。

第八节:行锁和表锁
1、插入测试数据

drop table if exists person;
 
create table person (
id int primary key,
`name` varchar( 20 )
);

insert into person values(1,'A');

insert into person values(2,'B');

insert into person values(3,'C');

2、事务设置

查看是否是自动提交,如下所示:


select @@autocommit;

然后,取消SQL语句的自动提交功能


set autocommit=0;

3、select的表锁和行锁

默认情况下,select语句是不会对数据加写锁的,也就是不会阻止写入(update delete),通过使用 for update可以对数据加写锁。

3.1 行锁

如果是按照主键查询的话,那么加的是行锁。请看下面的验证过程。

第一步:在第一个MySQL连接窗口中,执行:


begin;
select * from person where id =1 for update;

第二步:在第二个MySQL连接窗口中,执行:


update person set name='a' where id=1 ;

可以看到第二个连接的update语句一直在等待

第三步:在第三个MySQL连接窗口中,执行:


update person set name='b' where id=2;

执行结果为,瞬间更新成功:

> Affected rows: 1 > 时间: 0.014s

说明按主键查询的话,加的是行锁。

第四步:在第一个MySQL连接窗口中,执行:


commit;

第一个连接commit之后,这时可以看到,第二个MySQL连接窗口中的update语句执行成功。

3.2 表锁

如果查询条件不是按主键查询,那么会对整个表加表锁。

第一步:在第一个MySQL连接窗口中,执行:


begin;
select * from person where name='a' for update;

第二步:在第二个MySQL连接窗口中,执行:


update person set name='c' where id=3;

可以看到,虽然连接1中where name='a'的条件只能匹配id=1的一行,但是锁定了整张表。

第三步:在第一个MySQL连接窗口中,执行:


commit;

第一个连接commit之后,这时可以看到,第二个连接中的update语句执行成功。

4、InnoDB行锁和表锁实现方式

InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁。在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

5、特殊情况

select * from person where id = (select * from person where name='A') for update;

where id = ()仍然是行锁,如果你换成 where id IN () 那么是表锁

6、小结

以select…for update为例,深刻认识表锁和行锁;

造成锁表和锁行的原因在于查询条件是否走索引来决定的。原因在于:行锁是基于索引实现的。

第九节:表连接
1、内连接

内连接也称为等同连接,返回的结果集是两个表中所有相匹配的数据,而舍弃不匹配的数据。也就是说,在这种查询中,DBMS只返回来自源表中的相关的行,即查询的结果表包含的两源表行,必须满足ON子句中的搜索条件。作为对照,如果在源表中的行在另一表中没有对应(相关)的行,则该行就被过滤掉,不会包括在结果表中。

2、外连接

外部连接的语法与内部连接几乎样,主要区别就是对于空值的处理接不需要两个表具有匹配记录,这样可以指定某个表中的记录总是放到结根据哪个表中的记录总是放到结果集中,外部连接分为3种类型:右外部链接(RIGHT OUTER JOIN)、左外部连接(LEFT OUTER JOIN)和全外部连接(FLOUTER JOIN)。

三者的共同点是都返回符合连接条件的数据,这点和内部连接是一样的,不同点在于它们对不符合连接条件的数据的处理。三者的不同点说明如下:

左外部连接还返回左表中不符合连接条件的数据。

右外部连接还返回右表中不符合连接条件的数据。

全外部连接还返回左表中不符合连接条件的数据及右表中不符合连接轻的数据,它其实是左外部连接和左外部连接的合集。

这里的左表和右表是相对于JOIN关键字来说的,位于JOIN关键字左侧的都被称为左表,而位于JOIN关键字右侧的表即被称为右表。

在左外部连接中,左表中所有的记录都会被放到结果集中,无论是否在有存在匹配记录。

与左外部连接正好相反,在右外部连接中不管是否成功匹配连接条件都会返回表中的所有记录。

第十节:utf8 和 utf8mb4 的区别

utf8 和 utf8mb4 的区别:http://www.mybatis.cn/archives/844.html

第十一节:SQL SELECT DISTINCT 语句

SQL SELECT DISTINCT 语句:http://www.mybatis.cn/archives/845.html

第十二节:SQL CONTACT() GROUP_CONCAT() 函数介绍

SQL CONTACT() GROUP_CONCAT() 函数介绍:http://www.mybatis.cn/archives/847.html

思考题

思考题1:MySQL 是否可以使用状态字段创建索引?

索引是棵树,需要分叉,如果这个状态字段只能分出两个叉或者几个叉,就不要当索引。本题目考察对第三节和第四节的深刻理解。如果不能给出合理答案,说明上述两节内容没有掌握,请务必再多花时间好好学习。

极简SQL(MyBatis中文官网) @ 2020年