学数据库怎么能不学 SQL 😎
将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合称为数据库。
DBMS 的种类
客户端/服务器
类型(C/S
类型)结构SQL 语句及其种类
SQL 的基本书写规则
CREATE TABLE <表名> (<列名1> <数据类型> <该列所需约束>, <列名2> <数据类型> <该列所需约束>, …… <该表的约束1>, <该表的约束2>, ……);
例:
CREATE TABLE Product (product_id CHAR(4) NOT NULL, product_name VARCHAR(100) NOT NULL, sale_price INTEGER , register_date DATE , PRIMARY KEY (product_id));
只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称,名称必须以半角英文字母开头;所有的列都必须指定数据类型
DROP TABLE <表名>;
添加列 ALTER TABLE <表名> ADD COLUMN <列的定义>
删除列 ALTER TABLE <表名> DROP COLUMN <列的名称>
INSERT INTO <表名> VALUES (<value1>,<value2>,……);
SELECT <列名>,…… FROM <表名>;
SELECT * FROM <表名>;
SELECT product_id AS id, product_name AS name FROM Product;
使用双引号可以设定包含空格或中文的别名
SELECT product_id AS "商品编号", product_name AS "商品名称" FROM Product;
使用 DISTINCT 关键字
SELECT DISTINCT product_type FROM Product;
使用 DISTINCT 时,NULL 也被视为一类数据,NULL 存在于多行中时,也会被合并为一条 NULL 数据 DISTINCT 也可以在多列之前使用,会将多个列的数据进行组合,将重复的数据合并为一条。此时 DISTINCT 关键字只能用在第一个列名之前
SELECT 语句通过 WHERE 子句来指定查询数据的条件
SELECT <列名>,…… FROM <表名> WHERE <条件表达式>;
单行注释: 书写在“--”之后,只能写在同一行。MySQL 中需要在“--“后加入半角空格 多行注释: 书写在/和/之间,可以跨多行
所有包含 NULL 的计算,结果肯定是 NULL
<>:不等于 =:大于等于 <=:小于等于
SQL 除了真、假外,还存在第三个逻辑值——不确定(UNKNOWN)
COUNT:计算表中的记录数 SUM:计算表中数值列中数据的合计值 AVG:计算表中数值列中数据的平均值 MAX:求出表中任意列中数据的最大值 MIN:求出表中任意列中数据的最小值
SELECT <列名1>,<列名2>,<列名3>,…… FROM <表名> GROUP BY <列名1>,<列名2>,<列名3>,……;
GROUP BY 子句中指定的列称为聚合键或者分组列。当聚合键中包含 NULL 时,在结果中会以“不确定”行(空行)的形式表现出来
HAVING 子句用来为聚合结果指定条件
SELECT <列名1>,<列名2>,<列名3>,…… FROM <表名> GROUP BY <列名1>,<列名2>,<列名3>,…… HAVING <分组结果对应的条件>;
WHERE 子句用来指定数据行的条件,HAVING 子句用来指定分组的条件
SELECT <列名1>,<列名2>,<列名3>,…… FROM <表名> ORDER BY <排序基准列1>,<排序基准列2>,……(DESC);
书写顺序: 1.SELECT 子句 →2.FROM 子句 →3.WHERE 子句 →4.GROUP BY 子句 → 5. HAVING 子句 → 6. ORDER BY 子句
执行顺序: FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY
INSERT INTO <表名> (列1, 列2, 列3, ……) VALUES (值1, 值2, 值3, ……);
INSERT INTO ProductCopy (product_id, product_name, product_type, sale_price, purchase_price, regist_date) SELECT product_id, product_name, product_type, sale_price, purchase_price, regist_date FROM Product;
执行该 INSERT…SELECT 语句时,可以将 Product 表中这六列的数据插入到 ProductCopy 表中。在上述语句中,也可以使用 WHERE 子句或者 GROUP BY 子句等
INSERT INTO ProductType (product_type, sum_sale_price, sum_purchase_price) SELECT product_type, SUM(sale_price), SUM(purchase_price) FROM Product GROUP BY product_type;
将 Product 表中的数据按商品种类进行聚合,计算出销售单价合计值以及进货单价合计值,将结果存储在 ProductType 表中
DELETE FROM <表名>;
DELETE FROM <表名> WHERE <条件>;
使用 UPDATE 语句可以更改表中的数据
UPDATE <表名> SET <列名1> = <表达式1>, <列名2> = <表达式2>, …… WHERE <条件>;
事务是需要在同一个处理单元中执行的一系列更新处理的集合。通过使用事务,可以对数据库中的数据更新处理的提交和取消进行管理 MySQL 中更新商品信息的事务
START TRANSACTION; -- 将运动T恤的销售单价降低1000日元 UPDATE Product SET sale_price = sale_price WHERE product_name = '运动T恤'; -- 将T恤衫的销售单价上浮1000日元 UPDATE Product SET sale_price = sale_price WHERE product_name = 'T恤衫'; COMMIT;
各个 DBMS 的事务开始语句不相同,但事务结束语句都是 COMMIT 和 ROLLBACK
COMMIT——提交处理 提交事务包含的全部更新处理的结束指令,相当于文件处理中的覆盖保存。一旦提交,就无法恢复到事务开始前的状态 ROLLBACK——取消处理 取消事务包含的全部更新处理的结束指令,相当于文件处理中的放弃保存。一旦回滚,数据库就会恢复到事务开始之前的状态 **事务处理的模式:**SQL Server、PostgreSQL 和 MySQL 默认使用自动提交模式,每条 SQL 语句就是一个事务,相当于每一条语句都自动包含在事务的开始语句和结束语句之间 Oracle 直到用户执行 COMMIT 或者 ROLLBACK 为止算作一个事务
表中存储的实际数据,而视图中保存的是从表中取出数据所使用的 SELECT 语句。我们从视图中读取数据时,视图会在内部执行该 SELECT 语句并创建出一张临时表
CREATE VIEW 视图名称(<视图列名1>, <视图列名2>, ……) AS <SELECT语句>
DROP VIEW 视图名称;
子查询就是将用来定义视图的 SELECT 语句直接用于 FROM 子句当中
视图 ProductSum 和确认用的 SELECT 语句
-- 根据商品种类统计商品数量的视图 CREATE VIEW ProductSum (product_type, cnt_product) AS SELECT product_type, COUNT(*) FROM Product GROUP BY product_type; -- 确认创建好的视图 SELECT product_type, cnt_product FROM ProductSum;
子查询
SELECT product_type, cnt_product FROM (SELECT product_type, COUNT(*) AS cnt_product FROM Product GROUP BY product_type) AS ProductSum;
两种方法得到的结果完全相同
标量就是单一的意思。标量子查询必须而且只能返回 1 行 1 列的结果 要查询出销售单价高于平均销售单价的商品,由于在 WHERE 子句中不能使用聚合函数,因此以下的写法是错误的
SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > AVG(sale_price);
用标量子查询替换聚合函数
SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product);
能够使用常数或者列名的地方,无论是 SELECT 子句、GROUP BY 子句、HAVING 子句,还是 ORDER BY 子句,几乎所有的地方都可以使用
在细分的组内进行比较时,需要使用**关联子查询选取出各商品种类中高于该商品种类的平均销售单价的商品。**如果用以下方式
SELECT product_id, product_name, sale_price FROM Product WHERE sale_price > (SELECT AVG(sale_price) FROM Product GROUP BY product_type);
会报错,因为该子查询不是标量子查询,会返回多行结果 使用关联子查询,在子查询中添加 where 子句的条件
SELECT product_type, product_name, sale_price FROM Product AS P1 WHERE sale_price > (SELECT AVG(sale_price) FROM Product AS P2 WHERE P1.product_type = P2.product_type GROUP BY product_type);
在 WHERE 子句中追加了P1.product_type = P2.product_type
这个条件,该条件的意思是在同一商品种类中对各商品的销售单价和平均单价进行比较,使得 AVG 函数按照商品种类进行了平均值计算,即使在子查询中不使用 GROUP BY 子句也能得到正确的结果,但为了跟前面出错的查询进行对比,这里仍然加上了 GROUP BY 子句
增加一列每种商品种类平均售价
SELECT product_id, product_name, product_type, sale_price, (SELECT AVG(sale_price) FROM Product AS P2 WHERE P1.product_type = P2.product_type ) AS avg_sale_price FROM Product AS P1;
SELECT product_type, product_name, sale_price FROM Product AS P1 WHERE P1.product_type = P2.product_type AND sale_price > (SELECT AVG(sale_price) FROM Product AS P2 GROUP BY product_type);
如果将子查询中的关联条件移到外层查询中,会发生错误,因为这样违反了关联名称的作用域。 子查询内部设定的关联名称,只能在该子查询内部使用。 下图是子查询内的关联名称的有效范围
ABS(数值)
MOD(被除数,除数)
ROUND(对象数值,保留小数的位数)
CONCAT(字符串1,字符串2,……)
LENGTH(字符串)
MySQL 中,LENGTH 函数返回的是字节的长度,LENGTH(“山田”)返回的结果是 6,因为 UTF-8 编码中一个汉字占 3 个字节。若要在 MySQL 中计算字符的长度,请使用 CHAR_LENGTH 函数,CHAR_LENGTH(“山田”)返回的结果是 2LOWER(字符串)
UPPER(字符串)
REPLACE(对象字符串,替换前的字符串,替换后的字符串)
SUBSTRING(对象字符串 FROM 截取的起始位置 FOR 截取的字符数)
EXTRACT(日期元素 FROM 日期)
eg. EXTRACT(YEAR FROM CURRENT_TIMESTAMP)
该函数的返回值并不是日期类型,而是数值类型CAST(转换前的值 AS 想要转换的数据类型)
COALESCE(数据1,数据2,数据3,……)
返回参数中左侧开始第一个不是 NULL 的值谓词是满足返回值是真值这个条件的函数
前方一致查询
-- 选取出以“ddd”开头的字符串 SELECT * FROM SampleLike WHERE strcol LIKE 'ddd%';
中间一致查询
-- 选取出包含“ddd”的字符串 SELECT * FROM SampleLike WHERE strcol LIKE '%ddd%';
后方一致查询
-- 选取出以“ddd”结尾的字符串 SELECT * FROM SampleLike WHERE strcol LIKE '%ddd';
BETWEEN 数值1 AND 数值2
BETWEEN 的结果中会包含数值 1 和数值 2 这两个临界点
为了选取出某些值为 NULL 的列的数据,不能使用=,而只能使用特定的谓词 IS NULL
-- 取出进货单价为320、500、5000的商品 SELECT product_name, purchase_price FROM Product WHERE purchase_price = 320 OR purchase_price = 500 OR purchase_price = 5000;
和
SELECT product_name, purchase_price FROM Product WHERE purchase_price IN (320, 500, 5000);
两者等价
否定形式 NOT IN 可以取出进货单价不是 320、500、5000 的商品
-- 取得“在大阪店(000C)销售的商品的销售单价” SELECT product_name, sale_price FROM Product WHERE product_id IN (SELECT product_id FROM ShopProduct WHERE shop_id = '000C');
即使 ShopProduct(商店商品)表发生变化,同样的 SELECT 语句依然可以取出“在大阪店销售的商品的销售单价”,使程序变得易于维护
使用 EXIST 谓词实现与“IN 和子查询”相同的功能
-- 使用EXIST谓词选取出“大阪店在售商品的销售单价” SELECT product_name, sale_price FROM Product AS P WHERE EXISTS (SELECT * FROM ShopProduct AS SP WHERE SP.shop_id = ‘000C’ AND SP.product_id = P.product_id);
SELECT *
,即使改为SELECT 1
也可以。SELECT *
CASE <表达式> WHEN <表达式> THEN <表达式> WHEN <表达式> THEN <表达式> WHEN <表达式> THEN <表达式> …… ELSE <表达式> END
使用简单 CASE 表达式将字符串 A ~ C 添加到商品种类中
SELECT product_name, CASE product_type WHEN '衣服' THEN CONCAT("A:", product_type) WHEN '办公用品' THEN CONCAT("B:", product_type) WHEN '厨房用具' THEN CONCAT("C:", product_type) ELSE NULL END AS abc_product_type FROM Product;
简单 CASE 表达式的缺点是无法在 WHERE 子句中指定不同的列
CASE WHEN <求值表达式> THEN <表达式> WHEN <求值表达式> THEN <表达式> WHEN <求值表达式> THEN <表达式> …… ELSE <表达式> END
使用搜索 CASE 表达式将字符串 A ~ C 添加到商品种类中
SELECT product_name, CASE WHEN product_type = '衣服' THEN CONCAT('A:', product_type) WHEN product_type = '办公用品' THEN CONCAT('B:', product_type) WHEN product_type = '厨房用具' THEN CONCAT('C:', product_type) ELSE NULL END AS abc_product_type FROM Product;
搜索 CASE 表达式包含了简单 CASE 表达式的全部功能,所以一般都使用搜索 CASE 表达式
SELECT product_type, SUM(sale_price) AS sum_price FROM Product GROUP BY product_type;
执行结果
product_type | sum_price -------------+---------- 衣服 5000 办公用品 600 厨房用具 11180
如果要得到如下结果
sum_price_clothes | sum_price_kitchen | sum_price_office ------------------+-------------------+----------------- 5000 | 11180 | 600
可以在 SUM 函数中使用 CASE 表达式
-- 对按照商品种类计算出的销售单价合计值进行行列转换 SELECT SUM(CASE WHEN product_type = '衣服' THEN sale_price ELSE 0 END) AS sum_price_clothes, SUM(CASE WHEN product_type = '厨房用具' THEN sale_price ELSE 0 END) AS sum_price_kitchen, SUM(CASE WHEN product_type = '办公用品' THEN sale_price ELSE 0 END) AS sum_price_office FROM Product;
-- 用UNION选出两张表中所有的商品ID和商品名称 SELECT product_id, product_name FROM Product UNION SELECT product_id, product_name FROM Product2;
在集合运算符后添加 ALL 关键字,可以保留重复行
语法与 UNION 类似。UNION 相当于求两个表的并集,INTERSECT 相当于求两个表的交集
-- 用EXCEPT选出Product表中有而Product2表中没有的商品ID和商品名称 SELECT product_id, product_name FROM Product EXCEPT SELECT product_id, product_name FROM Product2 ORDER BY product_id;
EXCEPT 两个表的位置不一样,结果会不一样。因为减法运算中减数和被减数的位置不同,结果也不同
MySQL 尚不支持 INTERSECT 和 EXCEPT
UNION 是以行(纵向)为单位进行操作,而联结是以列(横向)为单位进行的。联结就是将其他表中的列添加进来,进行“添加列”的集合运算。
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM ShopProduct AS SP INNER JOIN Product AS P ON SP.product_id = P.product_id;
SELECT SP.shop_id, SP.shop_name, SP.product_id, P.product_name, P.sale_price FROM ShopProduct AS SP RIGHT OUTER JOIN Product AS P ON SP.product_id = P.product_id;
原则上联结表的数量并没有限制
FROM ShopProduct AS SP INNER JOIN Product AS P ON SP.product_id = P.product_id INNER JOIN InventoryProduct AS IP ON SP.product_id = IP.product_id
使用两次 INNER JOIN …… ON ……
窗口函数也称为OLAP 函数(Online Analytical Processing),可以对数据库数据进行实时分析处理。 目前 MySQL8.0 版本已经支持窗口函数
<窗口函数> OVER ([PARTITION BY <列清单>] ORDER BY <排序用列清单>) -- []中的内容可以省略
窗口函数大体可以分为以下两种:
/*根据不同的商品种类(product_type),按照销售单价(sale_price)从低到高的顺序排序*/ SELECT product_name, product_type, sale_price, RANK() OVER (PARTITION BY product_type ORDER BY sale_price) AS ranking FROM Product;
执行结果如下:
如果将整个表作为一个大的窗口来使用,无需指定 PARTITION BY,与使用没有 GROUP BY 的聚合函数时的效果一样
SELECT product_name, product_type, sale_price, RANK() OVER (ORDER BY sale_price) AS ranking FROM Product;
执行结果如下:
SELECT product_name, product_type, sale_price, RANK() OVER (ORDER BY sale_price) AS ranking, DENSE_RANK() OVER (ORDER BY sale_price) AS dense_ranking, ROW_NUMBER() OVER (ORDER BY sale_price) AS row_num FROM Product;
执行结果如下:
原则上窗口函数只能在 SELECT 子句中使用。因为窗口函数是对 WHERE 子句或者 GROUP BY 子句处理后的“结果”进行的操作,在 SELECT 子句之外使用窗口函数是没有意义的。
将 SUM 函数作为窗口函数使用
SELECT product_id, product_name, sale_price, SUM(sale_price) OVER (ORDER BY product_id) AS current_sum FROM Product;
执行结果如下:
我们得到的并不仅仅是合计值,而是按照 ORDER BY 子句指定的 product_id 的升序进行排列,计算出商品编号“小于自己”的商品的销售单价的合计值。因此,计算该合计值的逻辑就像金字塔堆积那样, 一行一行逐渐添加计算对象。 使用 AVG 的效果类似,计算平均值时作为统计对象的只是“排在自己之上”的记录。
在窗口中可以指定更加详细的汇总范围,该备选功能中的汇总范围称为框架
--指定“最靠近的3行”作为汇总对象 SELECT product_id, product_name, sale_price, AVG(sale_price) OVER (ORDER BY product_id ROWS 2 PRECEDING) AS moving_avg FROM Product;
执行结果如下:
ROWS 2 PRECEDING
就是将框架指定为“截止到之前 2 行”,也就是将作为汇总对象的记录限定为“自身(当前记录)、之前 1 行的记录、之前 2 行的记录”这最靠近的 3 行。
ROWS 2 FOLLOWING
表示“截止到之后 2 行“,ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
表示从之前 1 行开始到之后 1 行结束,即包括当前记录一共 3 行SELECT product_type, SUM(sale_price) AS sum_price FROM Product GROUP BY product_type WITH ROLLUP; --MySQL写法,标准SQL语句为ROLLUP(product_type)
执行结果如下:
ROLLUP 运算符相当于一次计算出不同聚合键组合的结果。上述代码实际上就是一次计算出了如下两种组合的汇总结果:
GROUP BY ()表示没有聚合键,会得到全部数据的合计行的记录,该合计行记录称为超级分组记录(super group row)
在聚合键中使用多个值
SELECT product_type, regist_date, SUM(sale_price) AS sum_price FROM Product GROUP BY product_type, regist_date WITH ROLLUP; --MySQL写法,标准SQL语句为ROLLUP(product_type, register_date)
执行结果如下:
相当于一次计算出如下三种组合的汇总结果:
如下图所示
GROUPING 函数在其参数列的值为超级分组记录所产生的 NULL 时返回 1,其他情况返回 0
SELECT GROUPING(product_type) AS product_type, GROUPING(regist_date) AS regist_date, SUM(sale_price) AS sum_price FROM Product GROUP BY product_type, regist_date WITH ROLLUP;
执行结果如下:
使用 GROUPING 函数和 CASE WHEN 子句在超级分组记录的键值中插入字符串
当 GROUPING 函数的返回值为 1 时,指定“合计”或者“小计”等字符串,其他情况返回通常的列的值
SELECT CASE WHEN GROUPING(product_type) = 1 THEN ‘商品种类 合计’ ELSE product_type END AS product_type, CASE WHEN GROUPING(regist_date) = 1 THEN ‘登记日期 合计’ ELSE regist_date END AS regist_date, SUM(sale_price) AS sum_price FROM Product GROUP BY product_type, regist_date WITH ROLLUP;
执行结果如下:
CUBE 语法和 ROLLUP 相同
SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计' ELSE product_type END AS product_type, CASE WHEN GROUPING(regist_date) = 1 THEN '登记日期 合计' ELSE regist_date END AS regist_date, SUM(sale_price) AS sum_price FROM Product GROUP BY CUBE(product_type, regist_date);
执行结果如下:
相当于一次计算出如下四种组合的汇总结果:
--只选取出将“商品种类”和“登记日期”各自作为聚合键的结果 SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计' ELSE product_type END AS product_type, CASE WHEN GROUPING(regist_date) = 1 THEN '登记日期 合计' ELSE regist_date END AS regist_date, SUM(sale_price) AS sum_price FROM Product GROUP BY GROUPING SETS (product_type, regist_date);
执行结果如下:
本文作者:青波
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!