SQL回顾与查漏补缺(一)- 基于MySql探讨一些注意点

原文链接: https://my.oschina.net/huangmc/blog/2414383

回顾了一遍SQL基础,集中总结一下,记录了一些平时容易忽略或者容易忘的问题。本篇博客中所有的范例都已在 MySql 5.7.20 中验证。

1. 关于查询基础

SELECT语句是SQL最基础也是最重要的语句,这里列一些自己以前没有注意到的东西。

首先我们先建立一张表作为示例,并随便插入一些值:

CREATE TABLE SubjectInfo
(
  id    INTEGER    NOT NULL ,
  name  VARCHAR(20) NOT NULL ,
  age   INTEGER    NOT NULL ,
  gender CHAR(6)    NOT NULL ,
  height INTEGER   NOT NULL ,
  weight INTEGER   NOT NULL ,
  PRIMARY KEY (id)
);

1.1 常数的查询

SELECT子句中不仅可以书写列名,还可以书写常数,比如:

SELECT name, age, lab AS 'ISMC' FROM SubjectInfo;

这样显示的结果除了选择的 name 和 age 列之外,还会增加一个 lab 列,结果中所有记录的值都会是我们指定的 ISMC 。

1.2 从结果中删除重复的行

在SELECT语句中使用 DISTINCT 可以删除重复行,但要注意的是:

  • NULL 也被视为一类数据,存在多条 NULL 数据行时,也会结合为一条 NULL 数据。
  • 可以在多列之前使用 DISTINCT :
    • 此时,此时多个列的数据进行组合,全部重复的会被删去
    • 注意 DISTINCT 关键字只能用在第一个列名之前

1.3 表达式中需要注意NULL

算术运算符:

  • 查询时,我们可以在子句中用上表达式,此时需要特别注意含有 NULL 的运算,比如:
    4 / NULL;
    NULL / 0;
    
    上式的计算结果都是 NULL,实际上所有包含 NULL 的计算,结果肯定是 NULL。

比较运算符:

  • 此外,不能对 NULL 使用比较运算符。在 WHERE 子句中我们利用>, <, <>筛选出来记录都不包含该条件列中为 NULL 的记录, 在SQL中提供了专门用来判断是否为 NULL 的运算符 IS NULLIS NOT NULL

逻辑运算符:

  • 真值通常是只有真(TRUE)和(FALSE)的,运算符在比较成立的时候返回真,不成立时返回假。但是,在SQL中还存在另外一个特定的真值--不确定(UNKNOWN)。也就是说,与通常的逻辑运算为二值逻辑相对,SQL中的逻辑运算是包含对真、假和不确定进行运算的三值逻辑。
  • 上述也是我们在前面提到的,为何用>, <, <>不能筛选出含有 NULL 值的记录的原因,因为此时的真值表不是只有2*2的4行,而是3*3的9行!因此,数据库领域的有识之士们达成了“尽量不使用 NULL ”的共识,这也是为什么我们在创建表时,会经常加上NOT NULL的字段限制条件。

2. 聚合、分组与排序

通过SQL对数据进行某种操作或者计算时需要使用函数,常用的函数包括COUNT, SUM, AVG, MAX, MIN,这些用于合计的函数称为聚合函数或者集合函数,实际上,所有的聚合函数都是一样的:输入多行输出一行。

2.1 聚合函数

COUNT函数:

  • 通常,聚合函数会对 NULL 以外的对象进行合计。但是只有 COUNT 函数例外,使用COUNT(*)可以查出包含 NULL 在内的全部数据行数。
  • 但是呢,将包含 NULL 的列作为参数时,COUNT(*)COUNT(<列名>)的结果并不相同。
  • 还有就是除了 COUNT 函数之外的其他函数并不能将 星号 作为函数的参数。

SUM、AVG函数:

  • 要注意在使用SUM(<列名>)AVG(<列名>)的时候,运算过程中是不包含该字段中值为 NULL 的记录的。

MAX、MIN函数:

  • SUM、AVG函数只能对数值类型的列使用,而MAX、MIN函数原则上适用于任何数据类型的类,也就是说,如果是能够排序的数据,就肯定有最大值和最小值,也就能够使用这两个函数。

2.2 分组

通过使用聚合函数和GROUP BY子句,可以将表分割后再进行聚合。在GROUP BY子句中指定的列称为聚合键或者分组列,聚合键可以是用逗号隔开的多个字段。当聚合键中包含 NULL 时,在结果中会以“不确定”行(空行)的形式表现出来。

2.2.1 使用GROUP BY子句时,SELECT子句中不能出现聚合键之外的列名

在使用 COUNT 这样的聚合函数时,SELECT子句中的元素有严格的限制,实际上,此时SELECT子句中只能存在以下三种元素:

  • 常数
  • 聚合函数
  • GROUP BY 子句中指定的列名(也就是聚合键)

这里常见的错误就是把聚合键之外的列名书写在SELECT子句中。这里的问题是聚合键和该字段并不是一一对应的。但是呢,在MySQL中这样的语法也得到了认同,执行时并不会发生错误,它会在多列候补中随机抽出一个满足条件的值。

2.2.2 不要再WHERE子句中使用聚合函数

考虑到WHERE子句是对记录的筛选,在其中使用聚合函数就不是对一行一行的筛选了,所以会引发错误。实际上,只有SELECT、HAVING 和 ORDER BY 中能够使用 COUNT 等函数。

2.2.3 WHERE 和 HAVING 的比较

两者都是指定条件用的,但是具体来说:WHERE 子句用来指定数据行的条件,HAVING 子句用来指定分组的条件。

HAVING子句和包含GROUP BY子句时的SELECT子句一样,能够使用的要素有一定的限制。限制的内容也完全一样,HAVING子句中能够使用的3种要素同样是:

  • 常数
  • 聚合函数
  • GROUP BY 子句中指定的列名(也就是聚合键)

在思考HAVING子句的使用方法时,把一次聚合后的结果作为HAVING子句起始点的话更容易把握。但此时就有一个疑问,因为我们发现聚合键所对应的条件此时既能在HAVIGN子句中使用,也可以在WHERE子句中使用。但是,我们认为聚合键所对应的条件还是应该放在WHERE子句中。理由有二:

  • 根本原因是WHERE子句和HAVING子句的作用不同:HAVING子句用来指定“组”的条件,而“行”所对应的条件应该写在WHERE子句中,这样一来,书写出来的SELECT子句中各部分各司其职,更易理解
  • 其实,关于DBMS的内部实现:通常情况下,为了得到相同的结果,将条件写在WHERE子句中要比写在HAVING子句中的处理速度更快,返回结果所需时间更短。
    • 使用COUNT函数等对表中数据进行聚合时,DBMS内部就会进行排序处理。排序处理会大大增加机器的负担,因此,只有尽可能减少排序的行数才能增加处理速度,而通过WHERE子句指定条件时,由于排序之前就对数据进行了过滤,所以能够减少排序的数据量。
    • WHERE子句更具速度优势的另一个理由是,可以对WHERE子句指定条件所对应的列创建索引,这样也可以大幅提高处理速度。

2.3 排序

不管何种情况,ORDER BY子句都需要写在SELECT BY子句都需要写在SELECT语句的末尾。这是因为对数据行进行排序的操作必须在结果即将返回时执行。ORDER BY子句中书写的列名称为排序键。在键后面可以使用DESC、ASC来指定降序、升序。排序键中包含NULL时,会在开头或末尾进行汇总,具体位置看具体DBMS。

此外,ORDER BY:子句中可以使用存在于表中,但并不包含在SELECT子句中的列,还可以使用聚合函数,比如说:

SELECT age, count(*)
  FROM subjectinfo
  GROUP BY age
  ORDER BY count(*);

3. 数据插入、删除和更新

在介绍这部分之前,我们先来了解清单的概念,比如:

INSERT INTO SubjectInfo 
  (id, name, age, gender, height, weight) 
VALUES 
  (1, 'Tristan', 23, 'male', 177, 71);

这种将列名和值用逗号隔开,分别写在()内的形式称为清单。

3.1 数据的插入(INSERT)

插入NULL时,需要在值清单中明确写出NULL,如果在列清单和VALUES中省略设定了默认值的列的话,该列的值就会被设定为NULL。插入默认值时,写DEFAULT,同时在定义表时,在对应的字段后面应该有``DEFAULT XXX`的设置。

省略列清单和多行INSERT: 对表中所有列进行INSERT操作时可以省略表明后的列清单,多行插入的时候可以将VALUES子句通过逗号进行分隔排列:

INSERT INTO SubjectInfo VALUES
  (1, 'Tristan', 23, 'male', 177, 71),
  (2, 'May', 23, 'female', 165, 52);

使用INSERT从其他表中复制数据: INSERT ... SELECT 语句可以在需要进行数据备份时使用。

INSERT INTO SubjectInfo
(id, name, age, gender, height, weight)
SELECT test_id, name, age, sex, h, w FROM AnotherTable;

3.2 数据的删除(DELETE)

标准SQL中用来从表中删除数据的只有DELETE语句。但是,很多数据库产品还存在一种被称为TRUNCATE的语句。这些产品主要包括Oracle、SQL Server、PostgreSQL 和 MySQL,而DB2中并没有TRUNCATE语句。

TRUNCATE <表名>

与DELECT不同的是,TRUNCATE只能删除表中的全部数据,而不能通过WHERE子句指定条件来删除部分数据。也正是因为它不能具体地控制删除对象,所以其处理速度比DELECT要快的多。实际上,DELETE语句在DML语句中也属于处理时间比较长的,因此要删除全部数据行时,使用TRUNCATE可以缩短执行时间。

3.3 数据的更新(UPDATE)

暂时没啥可说的

3.4 事务

在RDBMS中,事务代表了对表中数据进行更新的单位,简单来讲,事务就是需要在同一个处理单元中执行的一系列更新处理的集合。示例如下:

-- MySQL style
START TRANSACTION;  -- 事务开始 
UPDATE ...;
COMMIT;             -- 事务结束

其中,事务的终止指令包括 COMMIT(提交处理)和 ROLLBACK(取消处理)两种。

事务处理从何时开始:

事务并没有标准的开始指令存在,根据DBMS的不同而不同。实际上,几乎所有的数据库产品的事务都无需开始指令,这是因为大部分情况下,事务在数据库连接建立时就已经悄悄开始了,并不需要用户再明确发出开始指令。

那像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常有如下两种情况:

  • 每条SQL语句就是一个事务(自动提交模式)
  • 直到用户执行COMMIT或者ROLLBACK为止算作一个事务

通常的DBMS都可以选择其中任意一种模式。默认使用自动提交模式的DBMS有 SQL Server、Postgre SQL 和 MySQL等。该模式下,每一条语句都包括在事务的开始语句和结束语句之中。

ACID特性: DBMS的事务都遵循四种标准规格的约定。将这四种特性的首字母结合起来统称为ACID特性。这些约定是所有DBMS都必须遵守的规则。

  1. 原子性(Atomicity):指在事务结束时,其中所包含的更新处理要么全部执行,要么完全不执行。也就是要么占有一切要么一无所有。
  2. 一致性(Consistency):指的是事务中包含的处理,要满足数据库提前设置的约束,如主键约束或者 NOT NULL 约束等,一致性也称为完整性。
  3. 隔离性(Isolation):指保证不同事务之间互不干扰的特性。该特性保证了事务之间不会互相嵌套。此外,在某个事务中进行的更改,在该事务结束之前,对其他事务而言是不可见的。
  4. 持久性(Durability):指的是事务(不论是提交还是回滚)一旦结束,DBMS会保证该时点的数据状态得以保存的特性。即使由于系统故障导致数据丢失,数据库也一定能通过某种手段进行恢复。保证持久性的方法根据实现的不同而不同,其中最常见的就是将事务的执行记录(日志)保存在硬盘中,当发生故障时,可以通过日志恢复到故障发生前的状态。

其他方面

关于注释:

一行注释用--,多行注释用/* */,注意在MySql中单行注释--之后需要加入半角空格,否则不认为是注释。

逻辑运算符的优先顺序:

AND 运算符的优先级高于 OR 运算符,尽量用上括号以避免错误。

各子句的书写顺序和执行顺序:

书写顺序: SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY

执行顺序: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY

转载于:https://my.oschina.net/huangmc/blog/2414383

你可能感兴趣的:(SQL回顾与查漏补缺(一)- 基于MySql探讨一些注意点)