《SQL基础教程(第2版)Oracle》——(SQL- DML 及 DCL语言)

2. DML语言(Data Manipulation Language),数据操纵语言:

使用户能够查询以及变更已有数据库中记录的计算机语言。

2.1 SELECT:查询表中的数据

【有自己的名字:SQL查询语言,Data Query Language,DQL语言】

# 基本的SELECT语句
SELECT <列名>, ……   --可以为字段、常数、计算表达式 【见WHERE子句中的介绍】
	FROM <表名>;

# 查询全部的列(将按照CREATE TABLE语句的定义对列进行排序)
SELECT * FROM <表名>;  --星号代表全部列的意思

子句:是以 SELECT 或者 FROM 等作为起始的短语,是 SQL 语句的组成要素。
原则上:以子句为单位进行换行 (子句过长时, 为方便起见可以换行)。
插入空行(无任何字符的行)会造成执行错误。

SQL语法:浅析select之七大子句——陌上少年,且听这风吟
除SELECT外,SQL语句还有七大子句。
SQL子句的顺序不能改变,也不能互相替换。
而且必须按照下面 (1) - (7) 的顺序编写子句:
(1) from: 表示从某个表中筛选数据。
(2) join… on: 关联多表查询
(3) where: 在查询结果中筛选的条件
(4) group by: 分组依据
【GROUP BY后面还可以跟GROUPING运算符】
(5) having: 在分组统计结果中再次筛选
(6) order by: 排序,默认是升序
【asc代表升序(从小到大),desc 代表降序(从大到小)】
(7) limit: 分页显示结果。

SQL语句逻辑查询的粗略的执行顺序,具体的执行顺序根据 DBMS的不同而不同:
FROM
→ INNER JOIN / LEFT OUTER JOIN / RIGHT OUTER JOIN
→ ON
→ WHERE (不能使用聚合函数、窗口函数)
→ GROUP BY(不能使用聚合函数、窗口函数)
→ HAVING(可以使用聚合函数,不能使用窗口函数)
→ SELECT(可以使用聚合函数、窗口函数)
【也可能是UPDATE…SET(SET子句中可以使用聚合函数、窗口函数)或者DELETE】
→ DISTINCT(可以用在聚合函数字段参数的前面)
→ UNION (ALL) / INTERSECT (ALL) / EXCEPT(ALL) (或为MINUS)
→ ORDER BY(可以使用聚合函数、窗口函数)
(→ INSERT INTO,且当使用INSERT INTO…SELECT语句复制表时,前面使用ORDER BY子句无意义,不要使用)

逻辑查询,是一个术语名词,其意思是SQL查询进行处理时的原理和概念。理解SQL语句背后的查询逻辑是为了让我们把查询知识更好的融会贯通。
真正在执行的时候,数据库服务器引擎实行的是物理查询,也不会按部就班地按照逻辑查询的方式和步骤去执行。但它会在遵守“逻辑查询”的规范和要求的前提下,实行优化方案。
因此,虽然在分析逻辑查询的原理、概念和顺序时,表面上往往会看似效率低下。但其实它是理解数据库服务引擎中真正的物理查询过程和顺序的基础,及有效工具!

对于大多数编程语言,代码的执行是按照编写时的顺序处理的,但在SQL中有所不同。
就 SELECT来说,虽然它出现在语句的首部,有些DBMS也可能会在解析查询时首先考虑SELECT子句,但那是为了便于确定哪些列是可用的。
实际上,无论使用逻辑查询顺序还是物理查询顺序上来看, SELECT子句都不是被第一个处理的元素。

SQL的书写顺序和执行顺序都要清楚。
自己书写SQL时,应根据需求,按照执行顺序的思路,完成SQL各子句的书写,即:

①拆解需求:明确每个命令,即(每个需求,应该放在哪个子句中)及自己需要的顺序;
②再将每个命令按SQL的执行顺序写好(如先写FROM,再写WHERE…SELECT);
③并将其放到相应的书写位置上(如先是SELECT,再是FROM…WHERE…)

如此完成整个SQL.
阅读别人的SQL也是,要从执行顺序看其要实现什么样的功能。

2.1.1 FROM 子句

Oracle,DB2不允许省略SELECT语句中的FROM子句。
其它RDBMS是可以省略FROM子句的。

若只需要生成一行临时数据,在Oracle中,可以使用DUAL这个临时表。
在DB2中,可以使用 SYSIBM.SYSDUMMY1 这个临时表。
如:

SELECT (100 + 200) * 3 AS calculation  FROM DUAL;

在这里插入图片描述

【专题 #1:表的集合运算】

集合在数学领域表示 “(各种各样的)事物的总和” 。
在数据库领域中,集合表示记录(行)的集合。
表、 视图和查询的执行结果都是记录(行)的集合。

表的集合运算:对满足同一规则的两张表的记录(行)进行的加减等四则运算。
集合运算符:用来进行两表之间集合运算的运算符。

区别:
(1)表的集合运算:以记录行(纵向)为单位进行操作。会导致行的增减,但不会导致列的增减;
(2)表的联结运算:以字段列(横向)为单位进行操作。会导致列数的增加。

#1.1 并、交、差集合运算符
-- 并、交、差集合运算符的通用语法
<SELECT查询语句1>  -- 可扩展除ORDER BY的所有子句
   FROM <表名1>
<集合运算符>      -- 仅适用于UNION (ALL) / INTERSECT (ALL) / EXCEPT(ALL)
<SELECT查询语句2>  -- 可扩展除ORDER BY的所有子句
   FROM <表名2>
ORDER BY <排序基准列1>, <排序基准列2>, ……  -- 若需要,ORDER BY只能在最后使用一次

并、交、差集合运算符及通用运算要点:
(1)作为运算对象的记录的列数(即两个<SELECT查询语句>中的列数)必须相同;
(2) 作为运算对象的记录中列的类型(即两个<SELECT查询语句>中列的类型)必须一致。
从左侧开始,相同位置上的列必须是同一数据类型。
一定要使用不同数据类型的列时,可以使用类型转换函数CAST【详见CASE函数的介绍】。
(3)每个作为运算对象的 “<SELECT查询语句> ” 可以扩展除ORDER BY之外的所有子句。
若要使用ORDER BY ,只能在最后使用一次。【原因:执行顺序】
(4)运用集合运算符的结果,通常都会除去重复的记录。
若要在结果中保留重复行,在集合运算符后面添加ALL关键字即可。

并、交、差的集合运算符:
(1)UNION (ALL) :并集运算符,记录的加法运算符。
(2)INTERSECT (ALL) :交集运算符,选取两个记录集合中公共部分的运算符。

区别 AND 和 INTERSECT :
AND :可以选取出一张表中满足多个条件的公共部分;
INTERSECT:应用于两张表,选取出它们当中的公共记录。

(3)EXCEPT (ALL) :差集运算符,记录的减法运算符。

返回去除被减数(即前一个<SELECT查询语句1>)中与减数(即后一个<SELECT查询语句2>)的公共部分之后的剩余结果。

在减法运算中,被减数和减数的位置不同,所得到的结果也不相同。

在标准SQL中EXCEPT可以加ALL,如在SQL SERVER就全部可以使用。
MySQL中不支持EXCEPT.
Oracle中使用特有的 MINUS 代替EXCEPT,而且也不支持MINUS ALL.


#1.2 集合的乘法和除法

(4)CROSS JOIN:交叉联结运算符,集合的乘法运算符

交叉联结运算:将每个表全部列的每一行组合在一起,其结果是表的笛卡尔积。

任意张表的笛卡尔积都没有任何的联结条件限制,都是将每一个表的每一个记录(所有列一整行),依次与其它表的每一个记录(所有列一整行)直接连接在一起。

最终,笛卡尔积的记录总数为所有表记录数的乘积。

而且不论各表的顺序如何,最终得到的笛卡尔积都是完全相同的。
(所有列都相同,所有列的每一行记录也都相同)

在实际业务中并不会使用交叉联结运算:
一是其结果没有实用价值;
二是由于其结果行数太多,需要花费大量的运算时间和高性能设备的支持。

-- 两张表的交叉联结运算语法
SELECT <列名>, ……   --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>  --在Oracle中要省略AS
	CROSS JOIN  
	<表名2> AS <别名2>;  --在Oracle中要省略AS

-- 若要求三张及三张以上表的笛卡尔积,直接在后面增加关键字和表名称即可
-- 如:三张表的交叉联结运算语法
SELECT <列名>, ……   --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>  --在Oracle中要省略AS
	CROSS JOIN  
	<表名2> AS <别名2>;  --在Oracle中要省略AS
	CROSS JOIN  
	<表名3> AS <别名3>;  --在Oracle中要省略AS

进行交叉联结时无法使用内联结和外联结中所使用的 ON 子句, 这是因为交叉联结是对所有表中的全部记录行进行交叉组合,也正因此,笛卡尔积结果中的记录数通常是所有表中记录数的乘积。

但笛卡尔积是所有联结运算的基础。
【见2.1.2 表的联结运算】

(5)关系除法:
集合运算中的除法通常称为关系除法。
但并没有定义专用的运算符。

在数学领域,除法运算的结果(商)乘以除数就能得到除法运算前的被除数。
这样的规则在关系除法中也是成立的。
通过商和除数相乘,也就是交叉联结, 就能够得到作为被除数的集合了(有时也并不能恢复成完整的被除数,但原理是成立的。)。

在这里插入图片描述

-- 关系除法实例
-- 在Oracle中,选取出掌握所有3个领域的技术的员工
SELECT DISTINCT emp
  FROM EmpSkills ES1    -- 表EmpSkills,被除数
 WHERE NOT EXISTS
        (SELECT skill
           FROM Skills   -- 表Skills,除数
         MINUS           -- EXCEPT要换成MINUS
         SELECT skill
           FROM EmpSkills ES2  
          WHERE ES1.emp = ES2.emp);

在这里插入图片描述

上述代码逻辑查询的执行顺序如下:

  1. FROM EmpSkills ES1
    • 首先确定主查询的数据源,这里是EmpSkills表,别名为ES1。
      【所有行和列,因为还没有做任何操作。】
  2. SELECT skill FROM Skills
    • 子查询开始执行,首先选择Skills表中的所有技能。
  3. SELECT skill FROM EmpSkills ES2 WHERE ES1.emp = ES2.emp
    • 在MINUS操作之前,这个子查询再从EmpSkills表中选择与ES1中相同员工(emp)的技能。
  4. MINUS
    • 执行MINUS操作,从Skills表中的所有技能中排除那些与ES1中相同员工相关的技能。
  5. NOT EXISTS
    • 然后外层查询检查子查询的结果。
    • 如果子查询没有返回任何行(即没有找到ES1中员工缺少的技能),则NOT EXISTS条件为真,并且当前员工的emp会被包含在结果集中。
    • 如果子查询中返回了记录(即ES1中员工尚有缺少的技能),则NOT EXISTS条件为假,当前员工的emp则不会被包含在结果集中。
  6. WHERE NOT EXISTS
    • WHERE子句整体上用于过滤那些没有缺失Skills表中任何技能的员工。
  7. SELECT DISTINCT emp
    • 最后,主查询选择那些通过WHERE子句过滤后的不同员工(emp)。
  8. DISTINCT
    • 如果结果中有重复的员工ID,DISTINCT关键字确保它们只被列出一次。

在实际执行时,数据库优化器可能会调整这个顺序以提高性能。这被称作物理查询顺序。
逻辑查询顺序是物理查询顺序的前提和基础。

2.1.2 JOIN…ON 子句(表的联结运算)

表的联结运算定义:
将其他表中的列添加过来,进行 “ 添加列 ” 的运算。

具体来说:
就是以 “两张表中都包含的列” 作为桥梁,将 “ 只存在于一张表中的列” 中,满足 同样条件 的列,汇集到同一结果之中。

【这里的 “同样条件” 即:使用 “两张表中都包含的列” 所设定的条件。
(1)这样的条件称为 “联结条件” ,写在ON子句中,它能起到与WHERE相同的作用。
(2)联结条件中使用的 “两张表中都包含的列” ,称为 “联结键” 。】

区别:
(1)表的集合运算:以记录行(纵向)为单位进行操作。会导致行的增减,但不会导致列的增减;
(2)表的联结运算:以字段列(横向)为单位进行操作。会导致列数的增加。

表的联结运算种类及其运算符:
(1)内联接:INNER JOIN…ON
(2)外联结:又分为三种。【没有OUTER JOIN…ON】
左(外)联结:LEFT OUTER JOIN…ON
右(外)联结:RIGHT OUTER JOIN…ON
全(外)联结:FULL JOIN…ON / FULL OUTER JOIN…ON

SELECT <列名>, ……   --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>  --在Oracle中要省略AS
	<表的联结运算类型 + 关键字JOIN>  --INNER/LEFT OUTER/RIGHT OUTER JOIN等
	<表名2> AS <别名2>;  --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 若要进行三张及三张以上表的联结运算,直接在ON后面增加联结运算类型关键字和表名称即可
	-- 后面可扩展其它所有需要的子句:WHERE、GROUP BY、HAVING、ORDER BY

(1)所有类型的联结运算的书写位置: 必须在FROM和WHERE之间。
执行顺序也在FROM和WHERE之间。
即先要将联结操作全部执行完,再在 联结结果 的基础之上,执行记录的筛选等其它所有步骤。
(2)凡是使用列名的地方,都按照“<表的别名>.<列名>”的格式来书写。
虽然对于那些同时存在于两张表中的列,不指定表的别名也不会发生错误,但还是建议这样书写。
(3)ON子句不可省略。
(4)ON子句中的联结条件中可以使用所有的谓词,因为它们的返回值都为真值。
但实际应用中九成以上都用“=”.
(5)ON子句中需要书写多个 使用联结键设定的联结条件 时,可以使用AND、OR等逻辑运算符连接每个联结条件。
(6)ON子句后面可以扩展其它所有需要的子句:WHERE、GROUP BY、HAVING、ORDER BY.
根据执行顺序,这些子句都是在 联结之后的结果 的基础之上,进行操作。
(7)原则上对联结表的数量没有限制。

2.1.2.1 INNER JOIN…ON 子句(内联结)

应用最广泛的联结运算。

要点:
(1)内联结结果跟表的位置无关。
(2)“内”的含义:结果记录都来自于笛卡尔积。
但必须满足联结条件才能被选择。
(3)由(2)可得,内联结结果的获取步骤:
先求出两张表的笛卡尔积,然后筛选出其中满足联结条件的记录,即可得其内联结的结果。

-- 两表之间的内联结
SELECT <列名>, ……   --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>  --在Oracle中要省略AS
	INNER JOIN
	<表名2> AS <别名2>;  --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY


-- 若要进行三张及三张以上表的内联结运算,直接在ON后面增加INNER JOIN...ON和表名称即可
-- 如:三张表的内联结运算语法
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	INNER JOIN
	<表名2> AS <别名2>;                       --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	INNER JOIN           --也可以是其它表的联结运算关键字
	<表名3> AS <别名3>;                      --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY

要将业务需求和内联结在程序中的实现原理联系起来,才能用好内联结:

例如常用的:从Product 表中获取 ShopProduct(商店商品)表中,每个商品编号PRODUCT_ID对应的商品名称PRODUCT_NAME 和 商品类别PRODUCT_TYPE.

在这里插入图片描述
在这里插入图片描述

-- 从Product表中获取ShopProduct(商店商品)表中,每个商品编号对应的商品名称和商品类别
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.product_type
  FROM ShopProduct SP 
	INNER JOIN Product P
    ON SP.product_id = P.product_id;

在这里插入图片描述

这是上面内联结SQL语句的过时语法。
对于联结的过时语法和特定语法,不建议使用,但还是希望大家能够读懂。以便可以读懂之前人写的代码并进行维护。

-- 使用过时语法的内联结(结果与上面代码相同)
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.product_type
  FROM ShopProduct SP, Product P
 WHERE SP.product_id = P.product_id;
2.1.2.2 外联结

要点:
(1)必须但可任意选取出的一张表,将它规定为主表。
(2)同一张表,被设定为主表的方式有两种。可在前,可在后。
在前:使用左联结LEFT OUTER JOIN…ON,且将其写在LEFT左侧;
在后:使用右联结RIGHT OUTER JOIN…ON,且将其写在RIGHT右侧。
(3)外联结中对主表进行规定的作用,体现在记录是否会被全部输出上。
是主表,无论它位置在前还是在后(即无论是使用LEFT还是RIGHT来指定主表),都一定能输出该主表中的全部信息。所得到的结果也都完全相同。
且输出结果中,不会再有除主表中的全部记录外的其它记录。
(4)由(3)可得:外联结最终结果的行数,只会受主表中记录数的影响。
因此,相比内联结,外联结可以得到固定行数的结果。

外联结结果的获取步骤:
(1)先找到笛卡尔积中所有满足联结条件的记录,并选取上,作为外联结的第一部分结果。
(2)利用主表的主键,在笛卡尔积中,确定出主表中的每一个记录所在的记录集合。
(3)检查主表中的每一个记录,至少要在次表中匹配到一个(或者说“至少要在笛卡尔积的结果中存在一个”)满足联结条件的记录。
(4)如果主表中的某一条记录,在次表中一条也没有匹配到(或者说“在笛卡尔积的结果中一条也不存在”)满足联结条件的记录,那么就将次表中的字段值全部置为空NULL,与主表中的这条记录原本的信息一起,组成一行包含所有列的完整记录,选取上,作为外联结的另一部分结果中的一个。
按照同样的方式,处理主表中所有没有匹配到记录的记录。

“外”的含义: 即指结果记录中可能包含来自于各表笛卡尔积之外的信息。如(4).
例如:当主表中存在 次表中 不存在的记录 的时候。
【注意:不是主表比次表的记录数多,才会有在两原表之外的信息出现在结果中,根本原因是主表中存在 次表中不存在的记录,所以这些记录没有被匹配上!!】

2.1.2.2.1 LEFT OUTER JOIN…ON 子句(左(外)联结)

通常使用LEFT的情况会多一些,但使用RIGHT也没有问题。

-- 两表之间的左(外)联结
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	LEFT OUTER JOIN
	<表名2> AS <别名2>;                     --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY


-- 若要进行三张及三张以上表的左(外)联结运算,直接在ON后面增加LEFT OUTER JOIN...ON和表名称即可
-- 如:三张表的左(外)联结运算语法
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	LEFT OUTER JOIN
	<表名2> AS <别名2>;                       --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	LEFT OUTER JOIN           --也可以是其它表的联结运算关键字
	<表名3> AS <别名3>;                      --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY
2.1.2.2.2 RIGHT OUTER JOIN…ON 子句(右(外)联结)
-- 两表之间的右(外)联结
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	RIGHT OUTER JOIN
	<表名2> AS <别名2>;                     --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY


-- 若要进行三张及三张以上表的右(外)联结运算,直接在ON后面增加RIGHT OUTER JOIN...ON和表名称即可
-- 如:三张表的右(外)联结运算语法
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	RIGHT OUTER JOIN
	<表名2> AS <别名2>;                       --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	RIGHT OUTER JOIN           --也可以是其它表的联结运算关键字
	<表名3> AS <别名3>;                      --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY
2.1.2.2.3 FULL (OUTER) JOIN…ON子句(全(外)联结)

UNION 和 FULL (OUTER) JOIN…ON的区别——CSDN

区别UNION 和 FULL (OUTER) JOIN…ON:
(1)UNION:属于表的集合运算,以记录行(纵向)为单位进行操作。会导致行的增减,但不会导致列的增减。
返回两张表中所有记录的并集,且会除去重复的记录。
(2)FULL (OUTER) JOIN…ON :属于表的联结运算,以字段列(横向)为单位进行操作。会导致列数的增加。
全(外)联结是SQL中一种特殊的连接操作。全(外)联结的返回结果将包含左表及右表中的所有行。

根据联结条件:
①首先包括左表和右表中匹配上的行;
②其次,还包含每个表中在另一个表中没有匹配上记录的“剩余”行。
对于这些没有匹配上记录的行,没有匹配上的列的值通常设置为NULL.

即:全外连接包括了内联结、左联结和右联结的所有信息。
(1)表1和表2内联结的结果;
(2)以表1为主表,和表2进行外联结的结果;
(3)以表2为主表,和表1进行外联结的结果。

-- 两表之间的全(外)联结
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	FULL OUTER JOIN          -- OUTER 可省略
	<表名2> AS <别名2>;                     --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY


-- 若要进行三张及三张以上表的全(外)联结运算,直接在ON后面增加FULL OUTER JOIN...ON和表名称即可
-- 如:三张表的全(外)联结运算语法
SELECT <列名>, ……                          --可以为字段、常数、计算表达式,也可以是*号
	FROM <表名1> AS <别名1>                --在Oracle中要省略AS
	FULL OUTER JOIN          -- OUTER 可省略
	<表名2> AS <别名2>;                       --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
    FULL OUTER JOIN          -- OUTER可省略,且也可以是其它表的联结运算关键字
	<表名3> AS <别名3>;                      --在Oracle中要省略AS
	ON  <使用联结键设定的联结条件及其组合>;    --ON子句不可省略!
	-- 后面可扩展其它所有需要的子句:WHERE、GEOUP BY、HAVING、ORDER BY

2.1.3 WHERE 子句

# 语法
SELECT <列名1>, <列名2>, <列名3>, …… 
   FROM <表名>
 WHERE <对整个表中的查询条件或其组合>;

WHERE子句 和 HAVING子句 的区别是:
(1) WHERE是针对原表的原始数据筛选,后面不能接聚合函数(avg,sum,count,max,min)和 专用窗口函数等;
(2)HAVING是针对分组统计结果的再次筛选,后面可以接聚合函数。

SELECT 和 WHERE 子句中均可以使用常数或者计算表达式
【SELECT子句中还可使用单独的字段名。WHERE子句中不可以。】
计算表达式是指:包含四则算术、字符串拼接、比较、逻辑等运算的式子。

基本的SELECT语句后面也可能加 WHERE子句。
当存在WHERE子句时,程序首先通过 WHERE子句查询出符合指定条件的记录,然后再选取出SELECT语句指定的列。

若记录的字段值为NULL,那么针对该字段的某一比较运算,其返回的真值,就是除真假之外的第三种值——不确定(UNKNOWN)。
但对于WHERE子句来说,只有当运算结果为TRUE时,才会选择该记录;运算结果为FALSE和UNKNOWN的记录,都不会选择。所以在输出中看不见字段值为NULL的该条记录。
因此,对于NULL值:
在四则算术运算中:对NULL值及其运算结果有相应的规定;
在比较运算中:对NULL值设有专用的运算符;
在逻辑运算中:有应该遵循的一致性共识。

关于WHERE子句的补充:

(1)不要在WHERE子句中使用聚合函数和专用窗口函数。
只有SELECT子句、HAVING子句、ORDER BY子句中能够使用聚合函数。
只有在SELECT子句、ORDER BY子句、UPDATE语句的SET子句中,可以使用窗口函数(包括聚合函数和专用窗口函数。)
(2)由GROUP BY子句中的聚合键形成的筛选条件,不应该书写在用来筛选组的HAVING子句中;
而应该写到用于筛选整个表记录的WHERE子句当中。

即:在Oracle数据库中,如果是单独的:
聚合键 比较运算符 常数(‘<字符串常数值>’ / <整数常数值> / TO_DATE(‘<日期常数值>’,’ < fmat > ’ );
这样类型的条件,应该写在WHERE子句中,而不是GROUP BY子句中。

在这里插入图片描述在这里插入图片描述
【这样做的其它理由:
① 将条件写在WHERE 子句中,可以先对数据进行过滤,从而减少DBMS 内部使用聚合函数对表中数据进行聚合操作前,进行排序处理时,参与排序的行数,提高处理速度,缩短用时。
② WHERE子句比HAVING子句多的一个优势:
可以对WHERE子句指定条件所对应的列,创建索引,从而大幅提高处理速度。
创建索引是一种非常普遍 的提高DBMS性能的方法,效果也十分明显。】

2.1.3.1 四则算术运算

关于四则算术运算(+ -*/,只针对数值型):
(1)运算以行为单位执行。
(2)所有包含NULL的四则算术运算,结果肯定是NULL。
(3)即使对于5/0中“除数不能为0”的错误,NULL/0时不会发生错误,并且结果还是NULL。

若希望 NULL能像0一样,得到5 + NULL = 5这样的结果,可以使用 COALESCE函数——将NULL转换为其他值(0),然后再与数值相加。

SELECT  DISTINCT PRODUCT_NAME,SALE_PRICE,PURCHASE_PRICE,SALE_PRICE-PURCHASE_PRICE AS "a"
	FROM Product
 WHERE SALE_PRICE - PURCHASE_PRICE >= 500;

在这里插入图片描述

2.1.3.2 比较运算符

比较运算符 可以对字符、数字和日期等几乎所有数据类型的列和值进行比较,不仅限于数值型。
在这里插入图片描述

在Oracle中,在对日期进行比较时,仍要使用TO_DATE函数处理日期。
如:TO_DATE(‘2009-09-27’, ‘yyyy-mm-dd’)

SELECT  DISTINCT PRODUCT_NAME,PRODUCT_TYPE,REGIST_DATE
	FROM Product
 WHERE REGIST_DATE < TO_DATE('2009-09-27', 'yyyy-mm-dd');

在这里插入图片描述

2.1.3.2.1 对字符串使用不等号比较运算符

对字符串使用 大于或小于 不等号比较运算符时:
按照字典顺序比较字符串的大小。

在ASCII码表中:
0 ~ 9 是 48 ~ 57 ;A ~ Z 是 65 ~ 90 ;a ~ z 是 97 ~ 122.
即0 ~ 9,A ~ Z,a~z依次增大。
因此,以相同字符开头的单词比不同字符开头的单词更相近。
且对 定长字符串CHAR 和 可变长字符串VARCHAR(2) 都适用。

在这里插入图片描述

2.1.3.2.2 对NULL使用 IS NULL 和 IS NOT NULL运算符

对 NULL 使用 “等于=” 或 “不等于<>” 比较运算符时:
SQL不识别 “= NULL” 和 “<> NULL”
因此,查询NULL时不能使用比较运算符(=或者<>).

也不能对NULL使用其它比较运算符(>=、<=、>、<),SQL均不识别。

在希望选取NULL记录时,需要在WHERE条件表达式中使用 IS NULL 运算符。
在希望选取不是NULL的记录时,需要在WHERE条件表达式中使用 IS NOT NULL 运算符。

2.1.3.3 逻辑运算符(NOT、AND、OR)

逻辑运算符不能单独使用,必须和其他查询条件组合起来使用。
而其它查询条件中可能会包含比较运算。

逻辑运算和逻辑运算符,中的 “逻辑” 就是对真值进行操作的意思。
真值就是值为真(TRUE)或假(FALSE)其中之一的值。

比较运算符(=、<>、>=、<=、>、<)会把运算结果以真值的形式进行返回。
比较结果成立时返回真(TRUE),比较结果不成立时返回假(FALSE)。

逻辑运算符(AND、OR、NOT) 对 比较运算符 返回的真值(TRUE、FALSE),进行操作。
在这里插入图片描述

2.1.3.3.1 NOT 逻辑运算符

逻辑运算符NOT 用来否定某一条件。
使用在WHERE子句中,或者 HAVING子句中。

不使用NOT运算符也可以编写出效果相同的查询条件。不仅如此,不使用NOT运算符的查询条件更容易让人理解。因此,要注意不能滥用NOT.
但也不能完全否定NOT运算符的作用,在编写复杂的SQL语句时,经常会看到NOT的身影。

SELECT product_name, product_type, sale_price
	 FROM Product
 WHERE NOT sale_price >= 1000;    --与WHERE sale_price < 1000; 是等价的。   

在这里插入图片描述

2.1.3.3.2 AND 和 OR 逻辑运算符

多个查询条件进行组合时,使用AND运算符或者OR运算符。

AND运算符:在其两侧的查询条件都成立时整个查询条件才成立,其意思相当于“并且”。
其运算结果与乘法运算(积)的结果是一样的。因此,使用 AND 运算符进行的逻辑运算称为逻辑积

OR运算符:在其两侧的查询条件只要有一个成立,整个查询条件就成立, 其意思相当于“或者”。
其运算的结果与加法运算(和)的结果是一样的。因此,使用 OR运算符进行的逻辑运算称为逻辑和

AND运算符、OR运算符所在的行,可以另起一行。

AND运算符优先于OR运算符。
想要优先执行OR运算符时可以使用半角括号。

在这里插入图片描述

严格来说,此处的1+1=1与通常的整数运算并不相同。
只是真值没有第三个值,也就没有除0、1之外的2,所以才有了这样的结果。

2.1.3.3.3 含有NULL时的真值

若记录的字段值为NULL,那么针对该字段的某一比较运算,其返回的真值,就是除真假之外的第三种值——不确定(UNKNOWN)。
若还有其它与该比较运算组合的查询条件,根据表2-6,WHERE子句最终运算的返回值也可能是不确定(UNKNOWN)。
此时,对于WHERE子句来说,只有当运算结果为TRUE时,才会选择该记录;运算结果为FALSE和UNKNOWN的记录,都不会选择。
所以在输出中会发生看不见字段值为NULL的该条记录的情况。

真值只有 “真AND / 假FALSE”两种值的,其所进行的逻辑运算称为二值逻辑。
真值有 “真AND / 假FALSE / 不确定UNKNOWN” 三种值的,其所进行的逻辑运算称为三值逻辑。
在这里插入图片描述

如果某一字段/列需要进行逻辑运算,那么其字段值应尽量不使用NULL.
在创建表时,需要给该列设置NOT NULL约束。

在这里插入图片描述

【专题 #2:数据的聚合函数(进行汇总和计算)】

所谓聚合,就是将多行汇总为 一行:
①可能是将整个表的所有记录行,汇总为一行;
【即为没有GROUP BY时,每种聚合函数对表中所有记录的所有字段 进行的汇总】
②也可能是将整个表分组之后,每组中的所有记录行,汇总为一行。
【即为有GROUP BY时,GROUP BY对每组中的聚合键,以及每种聚合函数对分组后的每组中的所有字段 进行的汇总。】

聚合函数:输入多行,计算之后,输出一行。

聚合函数属于窗口函数的其中一种(另一种为专用窗口函数)。

(1)所有的聚合函数,如果以列名为参数,那么在计算之前就已经把NULL排除在外了。
因此,无论有多少个NULL 都会被无视。

但这与 “等价为 0” 并不相同,因为排除NULL之后,参与计算的记录个数也会显式地减少。
有时也想将NULL作为0进行计算,此时参与计算的记录个数就仍为该字段下的全部记录数(包括NULL值的个数)。

(2)所有的聚合函数都可以在参数中使用DISTINCT关键字,删除重复数据后,再进行相应计算。

(1)COUNT: 计算表中的记录数(行数)
COUNT(*) :计算表中全部列的记录数(包括NULL值),并返回。【参数为 * 是COUNT函数所特有的用法。】
COUNT(<列名>) :计算某一列中的非空(NULL之外的)行数。
想要计算值的种类时,可以在COUNT函数的参数中使用DISTINCT.

(2)SUM: 计算表中数值列中数据的合计值
(3)AVG: 计算表中数值列中数据的平均值

(4)MAX: 求出表中任意列中数据的最大值
(5)MIN: 求出表中任意列中数据的最小值
【数值,日期,字符串都可用。】

2.1.4 GROUP BY子句(进行分类及汇总)

【GROUP BY后面还可以跟GROUPING运算符】

# 使用GROUP BY子句进行分类及汇总的语法:
SELECT <列名1>, <列名2>, <列名3>, …… 
   FROM <表名>
GROUP BY <列名1>, <列名2>, <列名3>, ……;  --聚合键/分组列

# 或者:
SELECT <列名1>, <列名2>, <列名3>, …… 
   FROM <表名>
   WHERE <对整个表中的查询条件或其组合>
GROUP BY <列名1>, <列名2>, <列名3>, ……;  --聚合键/分组列

GROUP BY 子句的书写位置:
一定要写在FROM语句之后。

如果有WHERE子句的话需要写在WHERE子句之后。
书写顺序:1.SELECT → 2.FROM → 3.WHERE → 4.GROUP BY
此时,会先根据WHERE子句指定的条件进行过滤,然后再根据GROUP BY后面的聚合键/分组列进行分类及汇总处理。

GROUP BY和WHERE并用时SELECT语句的执行顺序:
FROM → WHERE → GROUP BY → SELECT

GROUP BY:用来分类及汇总,两种功能。
聚合函数:专用来汇总和计算,也是两种功能。
PARTITION BY:用来分类/分区,只有一种功能。【在使用窗口函数实现OLAP功能进行窗口定义中】

-- 从输出结果看GROUP BY具有分类及汇总两种功能的体现:
SELECT product_type
   FROM Product
 GROUP BY product_type;

在这里插入图片描述

分类作用: 不用多说,将原表中的所有记录按照product_type分为了绝对相互独立的三类。
汇总作用: 原表中共有8条记录,使用GROUP BY子句后,只剩下三条。
即将每组中的多个相同的聚合键值汇总为一条,共三组。
在这里插入图片描述

GROUP BY子句实现了:
①根据聚合键对原表进行分组(会生成一个临时表单独存放该结果);
②对分组后每组中的(多个相同的)聚合键值,进行汇总。 汇总后得到的新列,名称仍为聚合键的列名称,且也会被单独保存在临时表中。

在这里插入图片描述

不管SELECT子句中要求显示的是COUNT(*),还是COUNT(聚合键):
在聚合键列purchase_price中,取NULL的值总是被单独归为一类/一组(全是NULL值,在结果中会以“不确定”行(空行)的形式表现出来。),与其它所有取非NULL值的组(一定没有NULL值,且每组中该聚合键的取值一定都相等),完全分隔开。

分组之后的COUNT(*),求的是每组中包含NULL值的所有记录数。

【其实统计COUNT(*)的时候,只使用聚合键列这一列,及其每种取值的行数即可。为NULL的有几个,每一种不为NULL的,又有几个。
其它的字段/列仅仅是作为每一组中,(聚合键列每种不同的取值)不同数据的标识。】
在这里插入图片描述

分组之后的COUNT(聚合键),求的是每组中除NULL值之外的记录数。
在这里插入图片描述

所有取NULL值的记录已被单独归为一组,对这一组而言,因为全是NULL,因此包含NULL值的记录数COUNT(*)一定不为0,而除NULL值之外的记录数COUNT(聚合键)一定为0.

而其它组因为一定不包含NULL值,对这些非NULL值组而言,包含NULL值的记录数COUNT(*) 和 除NULL值之外的记录数COUNT(聚合键) ,一定是相等的。

“想要删除选择结果中的重复记录”时,使用DISTINCT.
所有的聚合函数都可以在参数字段前面使用DISTINCT关键字,
删除重复数据后,再进行相应计算。

“想要先按聚类键的取值将表分组之后,再计算各组的汇总结果”时,先使用GROUP BY.
之后也一定会用到聚合函数(否则就会让人产生疑问“进行分组的目的不是为了计算每组的统计值,那是为了什么?” “有必要进行分组吗?”)。

而也只有聚合函数具有:在分类结果上再次进行组内记录的汇总及计算的功能。
【不论是在与GROUP BY连用的情况下,还是作为窗口聚合函数来使用的情况下】
在与GROUP BY连用的情况下,聚合函数的位置可以是在SELECT子句、HAVING子句、ORDER BY子句中的任何一个/几个。
具体在哪一个/哪几个子句中,根据需求情况而定。

当然,也可以不进行分组,直接使用聚合函数对原表中的所有记录进行汇总和计算。

在这里插入图片描述

如上所述:
使用聚合函数的SELECT语句,不一定使用GROUP BY子句.
但使用GROUP BY子句时的SELECT子句中,一定会使用聚合函数。
则下面的“使用聚合函数时的SELECT子句”范围比“使用GROUP BY子句时的SELECT子句”范围更大。

而有HAVING子句,就一定会有GROUP BY子句。



使用聚合函数时的SELECT子句(整个黄色)
以及 使用聚合函数时的HAVING子句中(整个橙色),
都只能存在以下三种元素: 
● 常数 
● 聚合函数(COUNT、SUM、AVG、MAX、MIN)及其参数 --其参数也可以是除聚合键之外的其它列名GROUP BY子句中指定的列名(也就是聚合键)--如果没有GROUP BY子句时,则就没有这个
且在同一SQL语句中,SELECT子句中的要素必须 包含或等于 HAVING子句中的全部要素。

即:在Oracle数据库中:
使用聚合函数时的SELECT子句(整个黄色),打印出的列只能为/少于下面的列:
① '<字符串常数值>' AS <英文、下划线、数字字段名/列名> / "<中文字段名/列名>",<整数常数值> AS "<所有类型的字段名/列名>",
③ TO_DATE('<日期常数值>','<fmat>') AS "<所有类型的字段名/列名>",GROUP BY子句中指定的列名(也就是聚合键), --有GROUP BY子句时,才有这个COUNT(*), 
⑥ 聚合函数(<聚合键>)  --有GROUP BY子句时,才有这个
⑦ 聚合函数(<聚合键之外的其它字段/列名>) 

使用聚合函数时的HAVING子句(整个橙色),只能为下面两种情况中的一种:
① COUNT(*) 比较运算符 <整数常数值> ;
② 聚合函数(<聚合键>)  比较运算符  '<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');  
③ 聚合函数(<聚合键之外的其它字段/列名>) 比较运算符  '<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');
【如果是单独的:
④ 聚合键 比较运算符 常数('<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');)
这种类型的,那么应该把该句写在WHERE子句。】
--前两种的后面均还可能会有用逻辑运算符AND/OR连接的 其它的 也属于这三种类型的 命令

没有下面这个要求,不必理会:
因为HAVING子句本来就比SELECT子句先执行,而且两者完全是开分来执行的,没有这个限制:
在同一SQL语句中,HAVING子句中的 所有比较运算符左侧的 每一个表达式,即COUNT(*) 和 每一个 聚合函数(<聚合键>),聚合函数(<聚合键之外的其它字段/列名>) 要都能在SELECT子句中的列里找到。

聚合函数的作用是进行汇总及计算(输入多行,计算后输出一行),因此根据上面的三元素原则:
(1)当SELECT子句中有聚合函数,但SQL语句中没有GROUP BY子句时(也一定没有HAVING子句),
SELECT子句中所有的列名称,都只能出现在聚合函数的参数中;
(2)当SELECT子句中有聚合函数,SQL语句中也有GROUP BY子句时(可能有HAVING子句,也可能没有),
在SELECT子句中只能将聚合键作为单独的一列,其余的列名称都只能出现在聚合函数的参数中;

即:有GROUP BY子句时不要把聚合键之外的其它列名,作为单独要打印的一列,写在SELECT子句之中。
【只有MySQL认同这种语法, 所以能够执行,不会发生错误。(在多列候补中只要有一列满足要求就可以了)。
但是MySQL以外的 DBMS都不支持这样的语法,因此不要使用这样的写法。
原因:聚合键和其它的列并不一定是一对一的,没有设置优先表示规定的话,并不清楚除聚合键之外的列要显示哪个值。】

(3)当SELECT子句中有聚合函数,SQL语句中也有HAVING子句时(此时一定也有GROUP BY子句)
SELECTHAVING子句中的聚合函数参数可以是GROUP BY子句中的聚合键,也可以不是。





--以上三个的详解:
聚合函数的作用是进行汇总及计算(输入多行,计算后输出一行),因此根据上面的三元素原则,当SELECT子句中:
(1)当SELECT子句中有聚合函数,但SQL语句中没有GROUP BY子句时(也一定没有HAVING子句),SELECT子句中所有的列名称,都只能出现在聚合函数的参数中;

因为聚合函数首先具有汇总的功能,输入多行,输出一行。如果没使用GROUP BY子句(则也一定没有HAVING子句),那么SELECT子句中的聚合函数就会对原表中所有的数据//记录,进行汇总和计算。此时,对表中所有的字段都是一样的输入多行、只输出一行。
如果列字段不经任何处理计算,就直接跟聚合函数一起列在SELECT子句中,那它们输出的结果记录数(原记录数)一定比聚合函数的一行多,则就会发生错误。

而使用任意的聚合函数对任意满足数据类型要求的列进行处理,都可以实现输入多行、只输出一行。因此如果要对字段进行聚合函数的处理,SELECT子句中的使用任何聚合函数,且聚合函数的参数是哪个列字段都可以。

(2)当SELECT子句中有聚合函数,SQL语句中也有GROUP BY子句时(可能有HAVING子句,也可能没有),在SELECT子句中只能将聚合键作为单独的一列,其余的列名称都只能出现在聚合函数的参数中;

原因还是因为聚合函数具有汇总的功能。输入多行,输出一行。
GROUP BY子句具有分类和汇总(输入多行,输出一行)两种功能。
当使用GROUP BY子句时,先会根据聚合键对原表进行分组(⭐会生成一个临时表单独存放该结果)。得到的每个组的多个聚合键的值都是相等且唯一的,然后再将每组中的聚合键中的记录进行汇总,即将每组的多个聚合键值汇总成一个。
因此在GROUP BY子句执行完之后,还会生成的一个新的结果列,其中只包含每个聚合键种类中的一个值,该列中的记录数也就是聚合键的种类数。其名称仍为聚合键列的名称,⭐且也会被单独放在一个临时表中。因此,如果只调用聚合键列名,那么输出的实际上是其已经在每组中完成汇总的结果。
【⭐注意GROUP BY子句的分类和汇总这两个功能,是会被同时使用的。但其汇总的对象仅仅是分组后每组中的聚合键,其汇总时也像聚合函数那样,输入多行,只输出一行。汇总后的新列,名称仍为聚合键的名称,且会被单独保存。】
而每组中除聚合键列之外的字段,都还没有被进行汇总,都还在分组结果的临时表中保持着多个值。

SELECT子句中的聚合函数是在GROUP BY子句根据聚合键对原表进行分组的结果之上(保存分组结果的临时表中),对每组中的每个字段进行汇总(仍是将每组中每个字段的全部值汇总为一个,最终每个字段的总记录数也即为组数。)和计算。
【GROUP BY 子句本身就兼具对每组中的聚合键中的记录的汇总功能,所以原则上不需要再对每组中的聚合键列,进行额外的汇总操作。但如果是要使用聚合键原值之外的结果,如每组内的和SUM,以及每组中的记录个数COUNT(*)(包含空值)或者COUNT(聚合键)(不包含空值),则仍要对聚合键再次进行汇总。】
因此如果一直不对每组中除聚合键之外的字段进行汇总处理,在执行完SELECT子句等整个SQL语句之后,只有GROUP BY子句中使用的聚合键中的记录数,不需要再经过任何处理,就与聚合函数的结果行数是相同的。

而除聚合键之外的其它列字段下的记录数,也只有跟聚合函数的结果行数是相同的时,才能跟聚合函数一起列在SELECT子句中并输出,否则就无法输出。
它们的记录数要跟聚合函数的结果行数是相同的,那么就必须像聚合键一样,每组中所有的记录都汇总成一行。
此时,经常使用聚合函数,在GROUP BY子句根据聚合键对原表进行分组的结果之上(即在保存分组结果的临时表中)对每组中这些除聚合键之外的其它列字段进行汇总和计算,从而实现输入多行值,只输出一行结果。【每组中的聚合键的多个值,若需要也可以被再次汇总。】

【这只是在使用GROUP BY子句的情况下,SELECT子句中聚合键以及除聚合键之外的其它列字段的情况,未使用GRUOP BY 子句的情况,它们的汇总和计算情况,见(1.】

(3)当SELECT子句中有聚合函数,SQL语句中也有HAVING子句时(此时一定也有GROUP BY子句)SELECTHAVING子句中的聚合函数参数可以是GROUP BY子句中的聚合键,也可以不是。

首先,HAVING子句存在时,说明一定使用了GROUP BY子句。
其次,HAVING子句是 对原表进行分组 并且 对组内记录也进行汇总后的结果的筛选。
因此在其后面的筛选条件中,比较运算符左侧用的表达式,也只能是聚合键列(即GROUP BY已经对每组中原聚合键的记录值进行过的一次汇总,且被单独保存的结果列)或者以任意列字段为参数的汇总函数。

因此,在HAVING子句中,可以将聚合键作为单独的一列,以及 必须对除聚合键之外的其它列字段使用聚合函数处理的原因,同SELECT子句中的原因一样。可见(2.
【已经汇总过的聚合键,如果不需要就不用汇总了,可直接使用。
还没有进行过汇总的列,则要先按组对其进行所需要的汇总,得出在各个分组中汇总函数的值,
才能实现 对整个原表进行分组并对每个分组中的记录汇总后的结果  的筛选。】

【 “①对整个原表进行分组 并 ②对每个分组中的记录汇总 后的结果”
这个结果用户总是只能最后看到的结果,但实际上,在程序中内部要用两步才能得到。】
GROUP BY子句中,不能使用书写时在SELECT语句中定义的聚合键的别名。
【虽然这样的写法在PostgreSQL和MySQL都不会发生执行错误,但这并不是通常的使用方法。】

也不能根据执行顺序,先在GROUP BY子句中定义别名,然后在SELECT子句中使用。都会发生错误。
【因为GROUP BY子句本身就不具有使用AS关键字定义别名的功能。】
通常SELECT语句的执行结果的显示顺序都是随机的,
有GROUP BY子句时,执行结果也都是随机排序的。

因此想要按照某种特定顺序进行排序的话,需要在SELECT语句新子句ORDER BY中进行指定。

关于WHERE子句的补充: :见 2.1.3 WHERE子句

# GROUP BY子句 和 聚合函数的配合使用原则:
使G,就使聚;不聚,就不G。【注意顺序】

剩下的:
使聚,是否使G;不使G,是否使聚。
都根据需求而定。


# HAVING不会在没有GROUP BY时单独出现
# 加不加 HAVING 看需求GROUP BY子句,不一定有HAVING子句;
但有HAVING子句,就一定会有GROUP BY子句。

没有GROUP BY子句,就一定没有HAVING子句。
没有HAVING子句,不一定没有GROUP BY子句。

2.1.5 HAVING子句:在分组统计结果中再次筛选

WHERE子句 和 HAVING子句 的区别是:
见 2.1.3WHERE子句

# HAVING子句语法
SELECT <列名1>, <列名2>, <列名3>, ……
 FROM <表名>
 WHERE  <对整个表中的筛选条件或其组合>      --不需要时,可省略 WHERE子句
 GROUP BY <列名1>, <列名2>, <列名3>, ……
HAVING <分组结果对应的筛选条件>

HAVING 子句必须写在 GROUP BY 子句之后,其在 DBMS内部的执行顺序也排在GROUP BY子句之后。
使用HAVING子句时SELECT语句的顺序(书写SQL语句的顺序):
SELECT → FROM → WHERE → GROUP BY → HAVING

使用HAVING子句时SELECT语句的执行顺序:
FROM → WHERE → GROUP BY → HAVING → SELECT

在这里插入图片描述

如在GROUP BY子句中所述:
使用聚合函数的SELECT语句,不一定使用GROUP BY子句.
但使用GROUP BY子句时的SELECT子句中,一定会使用聚合函数。
下面“使用聚合函数时的SELECT子句”范围比“使用GROUP BY子句时的SELECT子句”范围更大。

而有HAVING子句,就一定会有GROUP BY子句。



使用聚合函数时的SELECT子句(整个黄色)
以及 使用聚合函数时的HAVING子句中(整个橙色),
都只能存在以下三种元素: 
● 常数 
● 聚合函数(COUNT、SUM、AVG、MAX、MIN)及其参数 --其参数也可以是除聚合键之外的其它列名GROUP BY子句中指定的列名(也就是聚合键)--如果没有GROUP BY子句时,则就没有这个
且在同一SQL语句中,SELECT子句中的要素必须 包含或等于 HAVING子句中的全部要素。

即:在Oracle数据库中:
使用聚合函数时的SELECT子句(整个黄色),打印出的列只能为/少于下面的列:
① '<字符串常数值>' AS <英文、下划线、数字字段名/列名> / "<中文字段名/列名>",<整数常数值> AS "<所有类型的字段名/列名>",
③ TO_DATE('<日期常数值>','<fmat>') AS "<所有类型的字段名/列名>",GROUP BY子句中指定的列名(也就是聚合键), --有GROUP BY子句时,才有这个COUNT(*), 
⑥ 聚合函数(<聚合键>)  --有GROUP BY子句时,才有这个
⑦ 聚合函数(<聚合键之外的其它字段/列名>) 

使用聚合函数时的HAVING子句(整个橙色),只能为下面两种情况中的一种:
① COUNT(*) 比较运算符 <整数常数值> ;
② 聚合函数(<聚合键>)  比较运算符  '<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');  
③ 聚合函数(<聚合键之外的其它字段/列名>) 比较运算符  '<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');
【如果是单独的:
④ 聚合键 比较运算符 常数('<字符串常数值>' / <整数常数值> / TO_DATE('<日期常数值>','<fmat>');)
这种类型的,那么应该把该句写在WHERE子句。】
--前两种的后面均还可能会有用逻辑运算符AND/OR连接的 其它的 也属于这三种类型的 命令

GROUP BY子句实现了:
①根据聚合键对原表进行分组(会生成一个临时表单独存放该结果);
②对分组后每组中的(多个相同的)聚合键值,进行汇总。 汇总后得到的新列,名称仍为聚合键的列名称,且也会被单独保存在临时表中。

在这里插入图片描述

把一次GROUP BY汇总后的结果作为HAVING子句起始点。

HAVING子句是 对整个原表进行分组 并且 对组内记录也进行汇总后的结果 的筛选。

用户总是只能最后看到“分组且对每组汇总后”的最终结果,但实际上,在程序中内部要用两步才能得到:
①对整个原表进行分组
②对每个分组中的记录汇总

因此,在HAVING子句中:
①已经在GROUP BY子句中汇总过的聚合键,如果不需要就不用汇总了,可直接使用。
②每个分组中除聚合键之外的列,都还没有进行过汇总。
则要使用聚合函数,先按组对它们进行所需要的汇总,得出在各个分组中这些列的聚合函数的值。
才能通过对每个分组中聚合函数值的筛选,实现对组的筛选。

根据执行顺序,标准SQL中不可以在HAVING子句中使用SELECT中定义的列的别名。
但 MySQL中支持。(强烈不推荐使用!)

Oracle中不支持。 Oracle中只能在ORDER BY中使用SELECT中定义的列的别名。

SELECT  purchase_price, COUNT(*),COUNT(product_type), AVG(sale_price)
	 FROM Product
 GROUP BY purchase_price
HAVING AVG(sale_price) >= 2500;

在这里插入图片描述

2.1.6 ORDER BY子句(排序,默认升序ASC)

【asc代表升序(从小到大),desc 代表降序(从大到小)】

# ORDER BY子句语法
SELECT <列名1>, <列名2>, <列名3>, …… 
  FROM <表名>
ORDER BY <排序基准列1>, <排序基准列2>, ……

因为对数据行进行排序的操作必须在结果即将返回时执行。
因此,不论何种情况,ORDER BY子句都需要写在SELECT语句的末尾。

子句的书写顺序
1.SELECT子句 → 2.FROM子句 → 3.WHERE子句
→ 4.GROUP BY子句 → 5.HAVING子句 → 6.ORDER BY子句

粗略的执行顺序:具体的执行顺序根据 DBMS的不同而不同
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

排序后,相同值的顺序,是随机排列的。
可以同时指定多个排序键及其顺序,对序列进行更细致的排序。
规则:
优先使用左侧的键,如果该列存在相同值的话,再接着参考右侧的键,直到能确定所有记录的顺序,或者是无排序键为止。

单个列名 / 排序键 后面使用ASC关键字:升序(可省略)
单个列名 / 排序键 后面使用DESC关键字:降序(不可省略)
可以同时指定一个列为升序,指定其他列为降序。

在标准SQL 中,关于NULL的顺序,只规定要“排列在开头或者末尾”,至于到底是开头还是末尾,以及显式地指定的方法,都依存于DBMS的实现。

使用含有NULL的列作为排序键时,NULL会在结果的开头或末尾汇总显示。

通过在ORDER BY子句中排序键的后面指定NULLS FIRST选项,可以显式地给DBMS下达指令,在排序时将NULL放在最前面。
ORDER BY <排序基准列1>, <排序基准列2>, …… NULLS FIRST
目前该方法也是在支持窗口函数的DBMS中通用的方法。但并不是标准SQL 支持的功能,仅依存于DBMS的实现。

因此,需要注意,这个功能随时都有可能因为某个DBMS的需求改变而无法继续使用。

在ORDER BY子句中:
(1)因为是在SELECT子句之后执行,因此可以使用SELECT语句中定义的别名。
(2)可以使用SELECT子句中未使用的列未使用的聚合函数及其参数
(3)还可以使用在SELECT子句中出现的列所对应的编号(也可在编号后面指定升降序关键字),但不推荐使用。

SELECT  product_type, COUNT(*) 
	 FROM Product
 GROUP BY product_type
ORDER BY AVG(purchase_price);

在这里插入图片描述

2.1.7 LIMIT子句

(7) limit: 分页显示结果。

limit m,n
m:表示从第几行开始取记录,第一个行的索引是0
n:表示该页最多显示几行
page表示第几页?
m = (page-1)×n?

2.1.8 AS:为列设定别名

别名中,若使用中文和空格时,需要用双引号(")括起来,否则会报错。
可以使用英文和下划线作为别名,不需要用引号引起来。

SELECT   <列名>  AS  <别名>,
          …… 
	FROM <表名>;

2.1.9 常数的查询(也使用到AS关键字)

常数有:字符串常数、数字常数、日期常数。

# 语法
SELECT '<常数值>' AS <字段名/列名>,...
	FROM <表名称>

在这里插入图片描述

在Oracle中,每种常数值都必须严格根据下面的形式设置别名,否则会报 “ORA-00923: 未找到要求的 FROM 关键字” 的错误。
【尤其是别名中的需要双引号,但没有添加时。】

# Oracle
SELECT '<字符串常数值>' AS <英文、下划线、数字字段名/列名> / "<中文字段名/列名>",
	   <整数常数值> AS "<所有类型的字段名/列名>",
	   TO_DATE('<日期常数值>','<fmat>') AS "<所有类型的字段名/列名>",
		...
	FROM <表名称>
SELECT  '商品' AS string, 38 AS "number",to_date('2009-02-24','yyyy-mm-dd') AS "date",
		 product_id, product_name
	FROM Product;

在这里插入图片描述

2.1.10 DISTINCT:删除重复行

# 语法
SELECT DISTINCT <字段名/列名>,<字段名/列名>,...
	FROM <表名称>

(1)DISTINCT关键字只能用在第一个列名之前。
(2)在使用DISTINCT时,NULL也被视为一类数据。
(3)DISTINCT在一列之前使用时,只要该列的值相等,就会被合并为一条。
因此结果中该字段下,一定不会有重复数据。

在这里插入图片描述

(4)DISTINCT也可以在多列之前使用。此时,会将多个列的数据进行组合,将重复的数据合并为一条。

只有后面多列中的每一个字段值都相等,才能算作两条数据相等,才会进行合并。
因此,结果中的每个字段下,都可能会有重复数据。
在这里插入图片描述

(5) 所有的聚合函数都可以在参数中使用DISTINCT关键字,删除重复数据后,再进行相应计算。


2.1.11 子查询

普通子查询:返回的记录,可以是任意的多列多行;
标量子查询:仅会被执行一次,并返回一行一列的单个值;
关联子查询:会被执行多次,每次返回一行一列的单个值。

好处:
如果在SELECT语句中使用了子查询,那么即使数据发生了变更,也还可以继续使用同样的SELECT语句。
这样也就减少了常规作业(单纯的重复操作)。

像这样可以完美应对数据变更的程序称为“易维护程序”,或者“免维护程序”。要有意识地编写易于维护的代码。

2.1.11.1 普通子查询(简称子查询)

子查询就是将用来定义视图的SELECT语句直接用于FROM子句当中。
子查询,是一次性视图(一次性SELECT语句)。因此子查询及其名称,在SELECT语句执行完毕之后就会消失。

凡是以 “AS 名称” 结尾的SELECT 语句一定是子查询。
子查询中的列名 可以是 已经经过计算的,最终要获取的字段的列名。

# 定义普通子查询的语法:
SELECT   <子查询列名1>, <子查询列名2>, ......  
 FROM   (<定义子查询使用的SELECT语句,FROM的表可以是实体原表,也可以是SELECT语句>)   
 AS   子查询名称; --【在Oracle中要省略AS.】
WHERE/GROUP BY/HAVING/ORDER BY  -- 若还需要,可以在子查询名称后面扩展所有子句

【一层/个子查询,至少有两个SELECT

(普通)子查询 书写必须要满足的要求:

(1)子查询FROM的表,必须是用SELECT语句来定义的。 不能是已存在的原始实体表。

如果某个SELECT语句后面FROM的表,是已经存在的原始实体表,那么这个SELECT语句,就是一个普通的SELECT查询语句

但这里(普通)子查询FROM的表,使用的SELECT语句本身:

①可以是普通的SELECT查询语句,即其FROM的表,可以是已经存在的原始实体表。
此时可直接扩展其它所有子句(WHERE、GROUP BY、HAVING 、ORDER BY )。
②也可以是其它子查询,即其FROM的表,仍然是用SELECT语句来定义的(此时已经三个SELECT了)。
此时如果要扩展其它子句,就必须满足下面(2)(3)的要求。

(2)子查询的FROM子句,最后必须添加 “AS 子查询名称” 结尾。
(3)子查询要扩展其它子句(如:WHERE/GROUP BY/HAVING/ORDER BY)时,必须写在 “FROM (SELECT …) AS 子查询名称” 的后面。
(4)<定义子查询使用的SELECT语句> 中的列名 <列1>, <列2>, … 必须和 其上一层子查询的列名 <子查询列名1>, <子查询列名2>, …(也可以使用 * 号代表全部列) 完全相同。

当<定义子查询使用的SELECT语句> 中的列为计算表达式时,则应为 该表达式 设置别名为 其上一层子查询中的对应的那个<子查询列名>。
【若其上一层子查询中使用了 * 号代表全部列,那么再往上一层找。】
且列的个数也必须相等。

【区别:创建视图时,<视图列名> 和使用的SELECT中的列名,不必完全相同。】

定义好子查询之后,整个子查询可以放在SQL语句的任何子句中的合适位置。
如作为IN谓词的参数。

# Oracle中两个普通子查询的例子:
# (1)没有嵌套的子查询
SELECT product_type, cnt_product
  FROM (SELECT product_type, COUNT(*) AS cnt_product
          FROM Product
         GROUP BY product_type) ProductSum;

# 是由创建视图的语句转化过来的
CREATE VIEW ProductSum (product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
  FROM Product
 GROUP BY product_type;



# (2)多层嵌套子查询
SELECT product_type, cnt_product
  FROM (SELECT *
          FROM (SELECT product_type, COUNT(*) AS cnt_product
                  FROM Product
                 GROUP BY product_type) ProductSum
         WHERE cnt_product = 4) ProductSum2;

(1)SQL是按照先内层查询,后外层查询的顺序来执行的。
子查询若作为内层查询,则会被首先执行。
(2)子查询的层数原则上没有限制,可以无限嵌套下去。
但随着子查询嵌套层数的增加,SQL 语句会变得越来越难读懂, 性能也会越来越差。因此,尽量避免使用多层嵌套的子查询。
(3)原则上子查询必须设定名称,根据处理内容来指定恰当的名称。
除Oracle数据库外,为子查询设定名称时需要使用 AS 关键字。
(4)创建普通的子查询时,就像CREATE VIEW创建视图一样,要知道它们都是独立的个体。
虽然子查询可以多层嵌套,但对每一个子查询来说,其都是一个可以完全分隔出来的整体。
而且不要因为名字里带一个“子”,就觉得子查询必须是谁的子句。

①如当最外层的查询语句也为子查询时,那这个子查询就是用来获得最终结果的,并不是谁的子句。
②再如标量子查询、关联子查询都不会独立充当某个子句的全部内容,它们都是某个子句中的一部分。

除上面两种情况之外,其它内部嵌套的子查询,属于某个子句应该是没问题的。
尤其可能是FROM或ORDER BY的子句。
即:它们可以独立充当FROM或ORDER BY子句的全部内容。

2.1.11.2 标量子查询

标量就是单一的意思。
标量子查询必须而且只能返回1行1列的结果。
即:标量子查询就是返回单一值的子查询。

# 定义标量子查询的语法:
SELECT   <单个字段或计算表达式>   
FROM   <表名>; 

标量子查询的书写位置:
通常任何可以使用单一值的位置都可以使用标量子查询。
也就是说,能够使用常数或者列名的地方,无论是 SELECT 子句、WHERE子句、GROUP BY 子句、HAVING 子句,还是 ORDER BY子句,几乎所有的地方都可以使用标量子查询。

如果子查询返回了多行结果,那么它就不再是标量子查询,而仅仅是一个普通的子查询了。就不能被用在使用单一值的位置。

# 三个标量子查询的例子
# (1)位于SELECT子句中
SELECT product_id, 
       product_name, 
       sale_price,
       (SELECT AVG(sale_price)
          FROM Product) AS avg_price
  FROM Product;

# (2)位于WHERE子句中
SELECT product_id, product_name, sale_price
  FROM Product
 WHERE sale_price > (SELECT AVG(sale_price)
                         FROM Product);
                         
# (3)位于HAVING子句中
SELECT product_type, AVG(sale_price)
  FROM Product
 GROUP BY product_type
HAVING AVG(sale_price) > (SELECT AVG(sale_price)
                              FROM Product);
2.1.11.3 关联子查询

关联子查询会在细分的组内进行比较时使用。
关联子查询和GROUP BY子句一样,也可以对表中的数据进行切分。
关联子查询结合条件如果未出现在子查询之中就会发生错误。

# 定义关联子查询的语法:
SELECT   <单个字段或计算表达式、函数等>   
FROM   <关联表名> AS <表别名>   --Oracle中中省略AS
WHERE <关联条件/结合条件>;    --以“<表别名>.<列名>”的形式表示字段
# Oracle中一个关联子查询的例子
SELECT product_type, product_name, sale_price
  FROM Product P1
 WHERE sale_price > (SELECT AVG(sale_price)
                         FROM Product P2
                        WHERE P1.product_type = P2.product_type
                      --【不需要添加子句:GROUP BY product_type】);

(1)同标量子查询一样,关联子查询可以出现在任何允许出现 单一值的位置。

但每个关联子查询会被重复执行多次。
见(4)关联子查询在DBMS中的实际执行过程的基本逻辑。

每个标量子查询仅会被执行一次。

如SELECT列表中,WHERE条件的运算表达式中等。
一文详解SQL关联子查询——猫来
(2)在对表中某一部分记录的集合进行比较时,就可以使用关联子查询。
因此,使用关联子查询时,通常会使用“限定(绑定)”或者“限制”这 样的语言,如限定“商品种类”对平均单价进行比较。
尤其,在细分的组内进行比较时,经常使用关联子查询。
(3)查询内部设定的表别名及关联名称,只能在该查询内部使用。
SQL是按照先内层查询,后外层查询的顺序来执行的。
因此,关联子查询的WHERE结合条件一定要写在内层关联子查询中,而不能写在外层查询中。
(4)关联子查询会对集合进行切分,但并不是分组。
关联子查询在DBMS中的实际执行过程的基本逻辑为:
对于外部查询(即外层SELECT语句)返回的每一行数据,内部查询(即关联子查询)都要执行一次。在关联子查询中是信息流是双向的。
(1)外部查询返回的每行数据传递一个值给关联子查询,
(2)然后关联子查询为每一行数据执行一次并返回它的结果。
(3)最后,外部查询根据关联子查询返回的结果执行,做出决策。
会重复计算关联子查询中的内容,但是这样效率并不会降低,因为SQL已经对此进行过优化。

如何正确理解SQL关联子查询——何大卫的博客
在这里插入图片描述

2.2 INSERT (ALL) INTO:向表中插入新数据

2.2.1 VALUES子句(INSERT INTO…VALUES)

# 标准SQL中,一次插入一行数据的INSERT语法:
-- DML语言,开始插入行的指令
INSERT INTO <表名> (1,2,3, ……) VALUES (1,2,3, ……);
-- INSERT INTO <表名>  列清单  VALUES  值清单;
  
  ....  --需要插多少条,重复多少次

COMMIT;  --DCL语言,确定插入行的指令语句

(1)列清单和VALUES子句中的值清单的列数必须保持一致。
(2)字符型和日期型数据,需要用单引号括起来。
(3)对表进行全列INSERT时,可以省略表名后的列清单。
这时VALUES子句的值会默认按照从左到右的顺序赋给每一列。
(4)插入NULL和默认值

只能为没有设置 NOT NULL 约束的字段(两种方法),插入空值。
如果为设置了 NOT NULL 约束的列,插入空值,INSERT语句就会出错。

只能为设置了默认值约束的字段(两种方法),插入默认值。

当在INSERT语句的列清单和值清单中都省略了某一列名,就会自动设定该列的值为默认值。
若该列没有被设置默认值,就会设定其值为NULL.

-- 为没有设置 NOT NULL 约束的字段,插入空值
-- 方法一:赋空值,直接在VALUES子句的值清单中写入NULL
-- 字段purchase_price和regist_date都没有设置 NOT NULL 约束
INSERT INTO ProductIns (product_id, product_name, product_type, purchase_price, regist_date) VALUES ('0010', 'T恤衫','衣服', NULL, NULL);

-- 方法二:直接省略要设为NULL的字段
-- 字段purchase_price和regist_date都没有设置 NOT NULL 约束
INSERT INTO ProductIns (product_id, product_name, product_type) VALUES ('0011', 'T恤衫','衣服');


-- 为设置了默认值约束的字段,插入默认值
-- 显示方法一(推荐使用):在VALUES子句中指定DEFAULT关键字
-- 显示插入sale_price的默认值(sale_price在创建表时已被设置了默认值约束)
INSERT INTO ProductIns (product_id, product_name, product_type, sale_price,  regist_date) VALUES ('0012', 'T恤衫','衣服', DEFAULT, TO_DATE('2009-09-20','YYYY-MM-DD'));

-- 隐式方法二:在列清单和 VALUES子句中的值清单 中都省略设定了默认值的列
-- 隐式插入sale_price默认值(sale_price在创建表时已被设置了默认值约束)
INSERT INTO ProductIns (product_id, product_name, product_type, purchase_price) VALUES ('0013', 'T恤衫','衣服', 500);

在这里插入图片描述

在Oracle中,若创建表时,设有日期类型的字段,那么在插入数据时,需要使用
to_date('<日期参数>', 'YYYY-MM-DD'),指定日期格式。
Oracle ORA-01861: 文字与格式字符串不匹配 解决方法—— 学软件的小垃圾

如创建Product表时:指定字段 regist_date 的数据类型为DATE型: regist_date DATE ,
那么,插入一行数据时,则INSERT 语句应为:

INSERT INTO Product VALUES ('0001', 'T恤' ,'衣服', 1000, 500, to_date('2009-09-20','yyyy-mm-dd'))

原则上,执行一次INSERT语句会插入一行数据。
插入多行时,通常需要循环执行相应次数的INSERT语句。
但其实很多RDBMS都支持 多行INSERT(multi row INSERT) 的功能:一次插入多行数据。

# 在 DB2/SQL Server/PostgreSQL/MySQL数据库中,一次插入多行数据的INSERT语法:
# VALUES子句前面省略了所有值清单统一对应的同一个列清单
INSERT INTO <表名> VALUES (<1>, <2>,  <3>, ......),
                          (<1>, <2>,  <3>, ......),
                         ......
                          (<1>, <2>,  <3>, ......);
# 在Oracle数据库中,一次插入多行数据的INSERT语法:
# 每个VALUES子句前面都省略了其值清单对应的列清单
INSERT  ALL  INTO <表名> VALUES (<1>, <2>,  <3>, ......)
             INTO <表名> VALUES (<1>, <2>,  <3>, ......)
             ......
             INTO <表名> VALUES (<1>, <2>,  <3>, ......)
SELECT * FROM DUAL;
--【INSERT ALL  INTO语法需要一个SELECT子句,但并不是从表中查询输入值。】

在Oracle数据库中,在VALUES前面插入的列清单:
1.可以全为空:代表每一行,都是要对表进行全列INSERT2.可以全不为空:
(1)若每一行都不为空且包括所有列,则同全为空的情况。
(2)若每一行只包括其中若干列,那么代表每一行,都只对列清单中的有的列进行INSERT.
3.也可以有的为空,有的不为空;

而且当所有行中的列清单有不为空的的时,这些不为空的列清单,其中的字段值也可以不相同。
【但在每一个列清单中,在CREATE TABLE 语句中设置了非空NOT NULL约束的字段都必须存在,
再加上没有设置NOT NULL约束但需要插入的列名,都要与后面值清单中的值一一对应】。

在插入多行时,'select 1FROM dual‘语句做了什么?——Stack Overflow用户

# 在Oracle中,向表 ProductIns 中执行多行INSERT
INSERT ALL INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0001', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0002', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0003', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns  VALUES ('0004', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns  VALUES ('0005', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns  VALUES ('0006', 'T恤衫','衣服', 1000, 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns (product_id, product_name, product_type, purchase_price, regist_date) VALUES ('0007', 'T恤衫','衣服', 500, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns (product_id, product_name, product_type, sale_price,  regist_date) VALUES ('0008', 'T恤衫','衣服', 1000, TO_DATE('2009-09-20','YYYY-MM-DD'))
		   INTO ProductIns (product_id, product_name, product_type, sale_price, purchase_price) VALUES ('0009', 'T恤衫','衣服', 1000, 500)
SELECT * FROM DUAL; 

在这里插入图片描述

2.2.2 SELECT子句(INSERT INTO…SELECT)

使用INSERT INTO…SELECT语句,能够从其他表中复制数据,可以在需要进行数据备份时使用。

# 语法
INSERT INTO <表名> (1,2,3, ……) SELECT <1>, <2>, <3>, …… FROM <其它表>; 

# 还可扩展SELECT的所有子句:WHERE、GROUP BY、HAVING、ORDER BY等
INSERT INTO <表名> (1,2,3, ……)
 SELECT <1>, <2>, <3>, …… 
  FROM <其它表>
  WHERE <对整个表中的查询条件或其组合>    --不需要时,可以省略
  GROUP BY <聚合键1>, <聚合键2>, <聚合键3>, ……     --不需要时,可以省略
  HAVING <分组结果对应的筛选条件>       --不需要时,可以省略
ORDER BY <排序基准列1>, <排序基准列2>, ……    --不需要时,可以省略

但即使指定了ORDER BY子句 也没有任何意义/效果,因为无法保证表内部记录的排列顺序。

【前后的列1、2、3名称可以不一样,但总列数要相同,系统自动一一对应】
前面的列1, 列2, 列3, …是插入到新表时,每个列的名称。
后面的列1, 列2, 列3, …是来自于其它表的字段,函数等列。

2.3 UPDATE…SET:更新表中的数据

# 语法
UPDATE <表名> SET <列名> = <表达式>;

列名:更新对象的列
表达式:更新后的值

2.3.1 WHERE子句(UPDATE…SET…WHERE)

# 语法:更新部分数据行的搜索型UPDATE
UPDATE <表名> 
   SET <列名> = <表达式>
 WHERE <条件>;

2.3.2 NULL清空

# 语法:
UPDATE <表名> 
   SET <列名> = NULL
 WHERE <条件>;

只有未设置 NOT NULL约束 和 主键约束 的列才可以清空为NULL。

不管是单个主键还是复合主键,只要某字段在主键约束的主键中,那么就不能被更新为NULL值。
原因见:1.1.2.2 列的约束及表的约束 – (3)针对表的主键约束

2.3.3 多列更新

# 语法:同时将多个列作为更新对象

# 写法一:使用逗号将列进行分隔排列
# 在所有的DBMS中都可以使用
UPDATE <表名> 
   SET <列名1> = <表达式1>, <列名2> = <表达式2>, ...
 WHERE <条件>;

# 写法二:将列和值都清单化
# 仅可以在PostgreSQL和DB2中使用。
UPDATE <表名> 
   SET (<列名1>, <列名2>,...) = (<表达式1>, <表达式2>, ...)
 WHERE <条件>;

2.4 DELETE:删除表中的数据

# 语法:清空表中全部数据行(全部列的全部行)
DELETE FROM <表名>;

# 等价于
TRUNCATE <表名>;  -- 在Oracle中,属于DDL语言,详见1.5-TRUNCATE介绍
# TRUNCATE后面不能跟任何子句,包括WHERE子句。
# 其处理速度比 DELETE 要快得多。

DELETE语句在 删除数据(行) 的同时会 保留数据表, 因此可以通过INSERT语句再次向表中插入数据。

区别DDL语言中的DROP TABLE:
DROP TABLE语句会完全删除整张表,且删除了的表是无法恢复的。
在执行DROP TABLE语句之前请务必仔细确认。
删除之后再想插入数据,就必须使用CREATE TABLE语句重新创建一张表。

oracle 误删除表的几种恢复方法——雨花石~

DELETE语句的对象是最原始表中的整行,而不是列,所以DELETE 语句无法只删除部分列的数据
(1)在 DELETE 语句中指定列名是错误的
(2)使用星号的写法(DELETE * FROM <表名>)也是不对的,同样会出错。
【 * 号代表的是全部列】

从表中删除部分指定列,要使用语法:
ALTER TABLE <表名> DROP COLUMN <列名>;
【而且必须至少留一列,不能删除表中的全部列,否则会报错】
在Oracle中要使用:
ALTER TABLE <表名> DROP (<列名>);

2.4.1 WHERE子句 (DELETE FROM…WHERE)

# 删除部分数据行的搜索型DELETE(删除全部列的某几行)
DELETE FROM <表名> WHERE <对整个表中的查询条件或其组合>;

# DELETE语句中不能扩展使用GROUP BY、HAVING和ORDER BY三类子句
# 下面这样的语句不能执行:
DELETE 【后面不能指定列名】
  FROM <表名>
 GROUP BY <聚合键1>, <聚合键2>, <聚合键3>, ……
 HAVING <分组结果对应的筛选条件>
 ORDER BY  <排序基准列1>, <排序基准列2>, ……; 
 
原因: GROUP BYHAVING是从表中选取数据时用来改变抽取数据形式的,
      ORDER BY是用来指定取得结果显示顺序的。
      在删除表中数据时,它们都起不到什么作用。

可以通过WHERE子句指定对象条件来删除 部分(包含所有列的)数据行

所有的删除总结:
P56: 删除整张表 这个对象:DROP TABLE <表名>;
P57: 删除所有记录的某几列【表中至少要留一列】:ALTER TABLE <表名> DROP COLUMN <列名>;

Oracle中要使用:ALTER TABLE <表名> DROP (<列名>) / (<列名>, <列名>, …);

P148: 删除全部列的全部行【清空数据,保留数据表】:DELETE FROM <表名>; 等价于TRUNCATE <表名>;
P149: 删除全部列的某几行:DELETE FROM <表名> WHERE <对整个表中的查询条件或其组合>;

无法只删除 部分记录的 部分字段/列。

3. DCL语言(Data control language),数据库控制语言:

用来确认或者取消对数据库中的数据进行的变更,包括COMMIT, ROLLBACK等语句。
除此之外,还可以用来设置或更改数据库用户或角色权限的语句,包括grant,deny,revoke等语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL.

3.1 COMMIT: 确认对数据库中的数据进行的变更

COMMIT是提交事务包含的全部更新处理的结束指令,相当于文件处理中的覆盖保存。
一旦提交,就无法恢复到本次事务开始前的状态了。
因此,在提交之前一定要确认是否真的需要进行这些更新。

# 事务的语法 
事务开始语句;
	DML语句①;   --(INSERT/UPDATE/DELETE语句)
	DML语句②;   --(INSERT/UPDATE/DELETE语句)
	DML语句③;   --(INSERT/UPDATE/DELETE语句)
	 . . .
事务结束语句(COMMIT:提交处理);

在这里插入图片描述
在这里插入图片描述

3.2 ROLLBACK: 取消对数据库中的数据进行的变更

ROLLBACK是取消事务包含的全部更新处理的结束指令,相当于文件处理中的放弃保存。
一旦回滚,数据库就会恢复到本次事务开始之前的状态,所有的处理都被取消了。

# 事务的语法 
事务开始语句;
	DML语句①;   --(INSERT/UPDATE/DELETE语句)
	DML语句②;   --(INSERT/UPDATE/DELETE语句)
	DML语句③;   --(INSERT/UPDATE/DELETE语句)
	 . . .
事务结束语句(ROLLBACK:取消处理);

在这里插入图片描述

3.3 CREATE ROLEE <角色名>:创建角色

3.4 GRANT: 赋予用户操作权限

3.5 REVOKE: 取消用户的操作权限

3.6 DENY:用于限制用户或角色对某些数据库对象的访问权限。

具体来说,它可以阻止用户或角色对某个表、视图、存储过程等对象的SELECT、INSERT、UPDATE、DELETE等操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值