Product表
CREATE TABLE Product
(product_id CHAR(4) NOT NULL,
product_name VARCHAR(100) NOT NULL,
product_type VARCHAR(32) NOT NULL,
sale_price INTEGER ,
purchase_price INTEGER ,
regist_date DATE ,
PRIMARY KEY (product_id));
插入数据
-- DML:插 入 数 据
BEGIN TRANSACTION;
INSERT INTO Product VALUES ('0001', 'T恤衫', '衣服', 1000, 500, '2009-09-20');
INSERT INTO Product VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
INSERT INTO Product VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
INSERT INTO Product VALUES ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
INSERT INTO Product VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
INSERT INTO Product VALUES ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20');
INSERT INTO Product VALUES ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28');
INSERT INTO Product VALUES ('0008', '圆珠笔', '办公用品', 100, NULL,'2009-11-11');
COMMIT;
第一章 数据库与SQL
表定义的更新(ALTER TABLE语句)
ALTER TABLE <表名> ADD COLUMN <列的定义>;
添加一列可以存储100位的可变长字符串的product_name_pinyin
列
-- Oracle和SQLServer中不用写`COLUMN`。
--Oracle
ALTER TABLE Product ADD (product_name_pinyin VARCHAR2(100));
--SQL Server
ALTER TABLE Product ADD product_name_pinyin VARCHAR(100);
删除列的ALTER TABLE语句
ALTER TABLE <表名> DROP COLUMN <列名>;
删除product_name_pinyin
列
ALTER TABLE Product DROP COLUMN product_name_pinyin;
变更表名:
--Oracle PostgreSQL
ALTER TABLE Poduct RENAME TO Product;
--DB2
RENAME TABLE Poduct TO Product;
--SQL Server
sp_rename 'Poduct', 'Product';
--MySQL
RENAME TABLE Poduct to Product;
第三章 聚合与排序
3-1 对表进行聚合查询
计算表中数据的行数
SELECT COUNT(*)
FROM Product;
------
8
计算NULL之外的数据的行数
如果想得到purchase_price
列(进货单价)中非空行数的话,
SELECT COUNT(purchase_price)
FROM Product;
------
6
第四章 数据的更新
4-1 数据的插入
从其他表复制数据
已存在表
的情况下:
-- 用来插入数据的商品复制表
CREATE TABLE ProductCopy
(product_id CHAR(4) NOT NULL,
product_name VARCHAR(100) NOT NULL,
product_type VARCHAR(32) NOT NULL,
sale_price INTEGER ,
purchase_price INTEGER ,
regist_date DATE ,
PRIMARY KEY (product_id));
-- 将商品表中的数据复制到商品复制表中
---- 简单写法
INSERT INTO ProductCopy
SELECT *
FROM Product;
---- 正常方法
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;
不存在表
的情况下(备份表操作
):
但是不保留主外键
SELECT * INTO ProductCopy20180502 FROM ProductCopy
第五章 复杂查询
5-2 子查询
标量子查询
标量就是单一的意思,在数据库之外的领域也经常使用
而标量子查询则有一个特殊的限制,那就是必须而且只能返回1行1列的结果。
标量子查询就是返回单一值的子查询。
例子:
-- 在WHERE子句中不能使用聚合函数
SELECT product_id, product_name, sale_price
FROM Product
WHERE sale_price > AVG(sale_price); --!报错:WHERE子句中不能使用聚合函数
WHERE子句中不能使用聚合函数,因此这样的SELECT语句是错误的。
下面是正确方式:
-- “查询出销售单价高于平均销售单价的商品。”
SELECT product_id, product_name, sale_price
FROM Product
WHERE sale_price > (SELECT AVG(sale_price)
FROM Product);
5-3 关联子查询
选取出各
条件:商品种类中高于该商品种类的平均销售单价的商品
1.按照商品种类计算平均价格
SELECT AVG(sale_price)
FROM Product
GROUP BY product_type;
返回结果是多行结果,所以不能使用标量子查询。
这个时候改使用关联子查询。
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);
该条件的意思就是,在同一商品种类中对各商品的销售单价和平均单价进行比较。
在对表中某一部分记录的集合进行比较时,就可以使用关联子查询。
在细分的组内进行比较时,需要使用关联子查询。
大家还记得我们用来说明GROUP BY子句的图
使用关联子查询进行切分的图示也基本相同
我们首先需要计算各个商品种类中商品的平均销售单价,由于该单价会用来和商品表中的各条记录进行比较,因此关联子查询实际只能返回1行结果。这也是关联子查询不出错的关键。
关联子查询执行时DBMS内部的执行情况
练习题:
5.1 创建出满足下述三个条件的视图(视图名称为ViewPractice5_1)。 使用Product(商品)表作为参照表,假设表中包含初始状态的 8行数据。
条件 1: 销售单价大于等于1000日元。
条件 2: 登记日期是2009年 9月 20日。
条件 3: 包含商品名称、销售单价和登记日期三列。
对该视图执行SELECT语句的结果如下所示。
SELECT * FROM ViewPractice5_1;
执行结果
product_name | sale_price | regist_date |
---|---|---|
T恤衫 | 1000 | 2009-09-20 |
菜刀 | 3000 | 2009-09-20 |
5.2 向习题 5.1 中创建的视图 ViewPractice5_1中插入如下数据,会得到什么样的结果呢?
INSERT INTO ViewPractice5_1 VALUES ('刀子', 300, '2009-11-02');
5.3 请根据如下结果编写 SELECT语句,其中 sale_price_all列为全部商品的平均销售单价。
product_id | product_name | product_type | sale_price | sale_price_all |
---|---|---|---|---|
0001 | T恤衫 | 衣服 | 1000 | 2097.5000000000000000 |
0002 | 打孔器 | 办公用品 | 500 | 2097.5000000000000000 |
0003 | 运动T恤 | 衣服 | 4000 | 2097.5000000000000000 |
0004 | 菜刀 | 厨房用具 | 3000 | 2097.5000000000000000 |
0005 | 高压锅 | 厨房用具 | 6800 | 2097.5000000000000000 |
0006 | 叉子 | 厨房用具 | 500 | 2097.5000000000000000 |
0007 | 擦菜板 | 厨房用具 | 880 | 2097.5000000000000000 |
0008 | 圆珠笔 | 办公用品 | 100 | 2097.5000000000000000 |
5.4 请根据习题5.1中的条件编写一条SQL语句,创建一幅包含如下数据的视图(名称为 AvgPriceByType)。
执行结果
product_id | product_name | product_type | sale_price | avg_sale_price |
---|---|---|---|---|
0001 | T恤衫 | 衣服 | 1000 | 2500.0000000000000000 |
0002 | 打孔器 | 办公用品 | 500 | 300.0000000000000000 |
0003 | 运动T恤 | 衣服 | 4000 | 2500.0000000000000000 |
0004 | 菜刀 | 厨房用具 | 3000 | 2795.0000000000000000 |
0005 | 高压锅 | 厨房用具 | 6800 | 2795.0000000000000000 |
0006 | 叉子 | 厨房用具 | 500 | 2795.0000000000000000 |
0007 | 擦菜板 | 厨房用具 | 880 | 2795.0000000000000000 |
0008 | 圆珠笔 | 办公用品 | 100 | 300.0000000000000000 |
提示 :其 中 的 关 键 是avg_sale_price
列。与习题5.3不同,这里需要计算出的是各商品种类的平均销售单价。这与5-3节中使用关联子查询所得到的结果相同。
也就是说,该列可以使用关联子查询进行创建。问题就是应该在什么地方使用这个关联子查询。
答案:
5.1
CREATE VIEW ViewPractice5_1
AS (SELECT product_name, sale_price, regist_date
FROM dbo.Product
WHERE sale_price >= 1000
AND regist_date = '2009-09-20'
)
5.2
对视图的更新归根结底是对视图所对应的表进行更新。因此,该INSERT语句实质上和下面的INSERT语句相同。
INSERT INTO Product (product_id, product_name, product_type, sale_price, purchase_price, regist_date)
VALUES (NULL, '刀子', NULL, 300, NULL, '2009-11-02');
5.3
SELECT product_id, product_name, product_type, sale_price,
( SELECT AVG(sale_price) FROM Product ) AS 'sale_Price_all'
FROM dbo.Product;
5.4
方案一:
SELECT product_id,product_name, product_type, sale_price,
(
SELECT AVG(sale_price) FROM Product P2
WHERE P1.product_type = P2.product_type --关键
GROUP BY P2.product_type
) AS avg_sale_price
FROM Product P1;
方案二:
SELECT p1.product_id, p1.product_name, p1.product_type, p1.sale_price,p2.avg_sale_price
FROM dbo.Product p1
LEFT JOIN
(SELECT product_type,AVG(sale_price) AS avg_sale_price FROM dbo.Product
GROUP BY product_type) p2
ON p1.product_type = p2.product_type
第六章 函数、谓词、CASE表达式
6-1 各种函数
字符串函数
创建表
-- DDL:创 建 表
CREATE TABLE SampleStr
(str1 VARCHAR(40),
str2 VARCHAR(40),
str3 VARCHAR(40))
-- DML:插 入 数 据
BEGIN TRANSACTION;
INSERT INTO SampleStr (str1, str2, str3) VALUES ('opx' ,'rt',NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('abc' ,'def' ,NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('山田' ,'太郎' ,'是我');
INSERT INTO SampleStr (str1, str2, str3) VALUES ('aaa' ,NULL ,NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES (NULL ,'xyz',NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('@!#$%' ,NULL ,NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('ABC' ,NULL ,NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('aBC' ,NULL ,NULL);
INSERT INTO SampleStr (str1, str2, str3) VALUES ('abc太郎' ,'abc' ,'ABC');
INSERT INTO SampleStr (str1, str2, str3) VALUES ('abcdefabc' ,'abc' ,'ABC');
INSERT INTO SampleStr (str1, str2, str3) VALUES ('micmic' ,'i' ,'I');
COMMIT;
- 拼接
+
或者||
-
LENGTH
——字符串长度 -
LOWER
——小写转换 -
REPLACE
——字符串的替换
REPLACE(对象字符串,替换前的字符串,替换后的字符串)
SELECT str1, str2, str3,
REPLACE(str1, str2, str3) AS rep_str
FROM SampleStr;
-
SUBSTRING
——字符串的截取
--(PostgreSQL/MySQL专用语法)
SUBSTRING(对象字符串 FROM 截取的起始位置 FOR 截取的字符数)
-- sqlserver专用
SUBSTRING(对象字符串,截取的起始位置,截取的字符数)
SELECT str1,
SUBSTRING(str1, 3, 2) AS sub_str
FROM SampleStr;
-
UPPER
——大写转换
日期函数
- 获取当前日期和时间
--SQL Server,PostgreSQL,MySQL
SELECT CURRENT_TIMESTAMP
--2018-05-03 17:21:07.763
- 获取当前日期
--PostgreSQL MySQL
SELECT CURRENT_DATE;
--2018-05-03
--SqlServer
SELECT CAST(CURRENT_TIMESTAMP AS DATE) AS CUR_DATE;
--2018-05-03
- 取得当前时间
--PostgreSQL MySQL
SELECT CURRENT_TIME;
--17:21:07.995+09
--SqlServer 使用CAST函数将CURRENT_TIMESTAMP转换为时间类型
SELECT CAST(CURRENT_TIMESTAMP AS TIME) AS CUR_TIME;
--17:21:07.7630000
- EXTRACT——截取日期元素
EXTRACT(日期元素 FROM 日期)
-- PostgreSQL MySQL
SELECT CURRENT_TIMESTAMP,
EXTRACT(YEAR FROM CURRENT_TIMESTAMP) AS year,
EXTRACT(MONTH FROM CURRENT_TIMESTAMP) AS month,
EXTRACT(DAY FROM CURRENT_TIMESTAMP) AS day,
EXTRACT(HOUR FROM CURRENT_TIMESTAMP) AS hour,
EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) AS minute,
EXTRACT(SECOND FROM CURRENT_TIMESTAMP) AS second;
--SQL Server (使用如下的DATEPART函数)
SELECT CURRENT_TIMESTAMP,
DATEPART(YEAR , CURRENT_TIMESTAMP) AS year,
DATEPART(MONTH , CURRENT_TIMESTAMP) AS month,
DATEPART(DAY , CURRENT_TIMESTAMP) AS day,
DATEPART(HOUR , CURRENT_TIMESTAMP) AS hour,
DATEPART(MINUTE , CURRENT_TIMESTAMP) AS minute,
DATEPART(SECOND , CURRENT_TIMESTAMP) AS second;
6-2 谓词
EXIST谓词
“判断是否存在满足某种条件的记录”,如果存在这样的记录就返回真(TRUE),如果不存在就返回假(FALSE)。
EXIST(存在)谓词的主语是“记录”。
① EXIST的使用方法与之前的都不相同。
② 语法理解起来比较困难。
③ 实际上即使不使用EXIST,基本上也都可以使用IN(或者NOT IN)来代替。
例子:使用EXIST选取出“大阪店在售商品的销售单价”
--exists谓词
SELECT p.product_name, p.sale_price
FROM dbo.Product p
WHERE exists (
SELECT product_id FROM dbo.ShopProduct sp WHERE shop_id = '000C'
AND p.product_id = sp.product_id
)
-- IN谓词
SELECT product_name, sale_price
FROM Product
WHERE product_id IN (SELECT product_id
FROM ShopProduct
WHERE shop_id = '000C');
product_name | sale_price |
---|---|
叉子 | 500 |
运动T恤 | 4000 |
菜刀 | 3000 |
擦菜板 | 880 |
- EXIST的参数
之前我们学过的谓词,基本上都是像“列 LIKE 字符串”或者“列 BETWEEN 值1 AND 值2”这样需要指定2个以上的参数,而EXIST的左侧并没有任何参数。很奇妙吧?这是因为EXIST是只有1个参数的谓词。EXIST只需要在右侧书写1个参数,该参数通常都会是一个子查询。
(
SELECT product_id FROM dbo.ShopProduct sp WHERE shop_id = '000C'
AND p.product_id = sp.product_id
)
上面这样的子查询就是唯一的参数。确切地说,由于通过条件SP.product_id = P.product_id
将Product
表和ShopProduct
表进行了联接,因此作为参数的是关联子查询。EXIST通常都会使用关联子查询作为参数
6-3 Case表达式
CASE表达式的语法分为简单CASE表达式和搜索CASE表达式两种。
--简单 CASE 语法
CASE <表达式>
WHEN <表达式> THEN <表达式>
WHEN <表达式> THEN <表达式>
WHEN <表达式> THEN <表达式>
. . .
ELSE <表达式>
END
--搜索 CASE 语法
CASE WHEN <求值表达式> THEN <表达式>
WHEN <求值表达式> THEN <表达式>
WHEN <求值表达式> THEN <表达式>
. . .
ELSE <表达式>
END
WHEN子句中的“<求值表达式>”就是类似“列 = 值”这样,我们也可以将其看作使用=、!=或者LIKE、BETWEEN等谓词编写出来的表达式。
例子:要得到如下结果
A:衣 服
B:办 公 用 品
C:厨 房 用 具
因为表中的记录并不包含“A: ”或者“B: ”这样的字符串,所以需要在SQL中进行添加。
SELECT CASE WHEN product_type = '衣服' THEN 'A:' + product_type
WHEN product_type = '办公用品' THEN 'B:' + product_type
WHEN product_type = '厨房用具' THEN 'C:' + product_type
ELSE NULL
END
FROM dbo.Product;
--简单CASE
SELECT CASE product_type WHEN '衣服' THEN 'A:' + product_type
WHEN '办公用品' THEN 'B:' + product_type
WHEN '厨房用具' THEN 'C:' + product_type
ELSE NULL
END
FROM dbo.Product;
CASE表达式的书写位置
CASE表达式的便利之处就在于它是一个表达式。之所以这么说,是因为表达式可以书写在任意位置,也就是像“1 + 1”这样写在什么位置都可以的意思。例如,我们可以利用CASE表达式将下述SELECT语句结果中的行和列进行互换。
要实现下面的结果:
sum_price_clothes | sum_price_kitchen | sum_price_office |
---|---|---|
5000 | 11180 | 600 |
上述结果是根据商品种类计算出的销售单价的合计值,通常我们将商品种类列作为GROUP BY子句的聚合键来使用,但是这样得到的结果会以“行”的形式输出,而无法以列的形式进行排列。
SELECT product_type,
SUM(sale_price) AS sum_price
FROM Product
GROUP BY product_type;
但是结果是:
product_type | sum_price |
---|---|
衣服 | 5000 |
办公用品 | 600 |
厨房用具 | 11180 |
我们可以像代码清单6-43那样在SUM函数中使用CASE表达式来获得一个3列的结果。
-- 对按照商品种类计算出的销售单价合计值进行 行列转换
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;
练习题:
按照销售单价(sale_price)对练习 6.1中的 Product (商品)表中的商品进行如下分类。
● 低档商品:销售单价在1000日元以下(T恤衫、办公用品、叉子、擦菜板、圆珠笔)
● 中档商品:销售单价在1001日元以上3000日元以下(菜刀)
● 高档商品:销售单价在3001日元以上(运动T恤、高压锅)
请编写出统计上述商品种类中所包含的商品数量的 SELECT语句,结果如下所示。
low_price | mid_price | high_price |
---|---|---|
5 | 1 | 2 |
答案:
SELECT
COUNT(CASE when sale_price<=1000 THEN sale_price END) AS low_price,
COUNT(CASE when sale_price>1001 AND sale_price<=3000 THEN sale_price END) AS mid_price,
COUNT(CASE when sale_price>3001 THEN sale_price END) AS high_price
FROM dbo.Product
第八章 SQL高级处理
8-1 窗口函数(OLAP函数 或 分析函数)
窗口语法
<窗口函数> OVER ([PARTITION BY <列清单>]
ORDER BY <排序用列清单>)
其中重要的关键字是PARTITION BY
和ORDER BY
,理解这两个关键字的作用是帮助我们理解窗口函数的关键。
① 能够作为窗口函数的聚合函数(SUM、AVG、COUNT、MAX、MIN)
②RANK
、DENSE_RANK
、ROW_NUMBER
等专用窗口函数
例子:根据不同的商品种类,按照销售单价从低到高的顺序创建排序表
product_name | product_type | sale_price | ranking |
---|---|---|---|
叉子 | 厨房用具 | 500 | 1 |
擦菜板 | 厨房用具 | 880 | 2 |
菜刀 | 厨房用具 | 3000 | 3 |
高压锅 | 厨房用具 | 6800 | 4 |
T恤衫 | 衣服 | 1000 | 1 |
运动T恤 | 衣服 | 4000 | 2 |
圆珠笔 | 办公用品 | 100 | 1 |
打孔器 | 办公用品 | 500 | 2 |
SELECT product_name, product_type, sale_price,
RANK () OVER (PARTITION BY product_type
ORDER BY sale_price) AS ranking
FROM Product;
PARTITION BY
能够设定排序的对象范围。本例中,为了按照商品种类进行排序,我们指定了product_type。ORDER BY
能够指定按照哪一列、何种顺序进行排序。为了按照销售单价的升序进行排列,我们指定了sale_price。此外,窗口函数中的ORDER BY与SELECT语句末尾的ORDER BY一样,可以通过关键字ASC/DESC来指定升序和降序。
窗口函数兼具分组和排序两种功能。
通过 PARTITION BY分组后的记录集合称为窗口。此处的窗口并非“窗户”的意思,而是代表范围。这也是“窗口函数”名称的由来。
无需指定PARTITION BY
SELECT product_name, product_type, sale_price,
RANK () OVER (ORDER BY sale_price) AS ranking
FROM Product;
product_name | product_type | sale_price | ranking |
---|---|---|---|
圆珠笔 | 办公用品 | 100 | 1 |
叉子 | 厨房用具 | 500 | 2 |
打孔器 | 办公用品 | 500 | 2 |
擦菜板 | 厨房用具 | 880 | 4 |
T恤衫 | 衣服 | 1000 | 5 |
菜刀 | 厨房用具 | 3000 | 6 |
运动T恤 | 衣服 | 4000 | 7 |
高压锅 | 厨房用具 | 6800 | 8 |
专用窗口函数的种类
-
RANK函数
计算排序时,如果存在相同位次的记录,则会跳过之后的位次。
例)有 3条记录排在第 1位时:1位、1位、1位、4位…… -
DENSE_RANK函数
同样是计算排序,即使存在相同位次的记录,也不会跳过之后的位次。
例)有 3条记录排在第 1位时:1位、1位、1位、2位…… -
ROW_NUMBER函数
赋予唯一的连续位次。
例)有 3条记录排在第 1位时:1位、2位、3位、4位……
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;
作为窗口函数使用的聚合函数
- 将SUM函数作为窗口函数使用
SELECT product_id, product_name, sale_price,
SUM(sale_price) OVER (ORDER BY product_id) AS current_sum
FROM Product;
计算该合计值的逻辑就像金字塔堆积那样,一行一行逐渐添加计算对象。在按照时间序列的顺序,计算各个时间的销售额总额等的时候,通常都会使用这种称为累计
的统计方法。
- 将AVG函数作为窗口函数使用
SELECT product_id, product_name, sale_price,
AVG (sale_price) OVER (ORDER BY product_id) AS current_avg
FROM Product;
计算移动平均
窗口函数就是将表以窗口为单位进行分割,并在其中进行排序的函数。其实其中还包含在窗口中指定更加详细的汇总范围的备选功能,该备选功能中的汇总范围称为框架。
需要在ORDER BY子句之后使用指定范围的关键字。
--指定“最靠近的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
(“行”)和PRECEDING(“之前”)两个关键字,将框架指定为“截止到之前~行 ”, 因 此 “ROWS 2 PRECEDING”就是将框架指定为“截止到之前2行”,也就是将作为汇总对象的记录限定为如下的“最靠近的3行 ”
这样的统计方法称为移动平均(moving average)。由于这种方法在希望实时把握“最近状态”时非常方便,因此常常会应用在对股市趋势的实时跟踪当中。
使用关键字FOLLOWING
(“ 之 后 ”) 替 换PRECEDING,就可以指定“截止到之后~行”作为框架了(图8-3)。
8-2 GROUPING运算符
同时得到合计行
使用GROUP BY无法得到合计行
SELECT product_type, SUM(sale_price)
FROM Product
GROUP BY product_type;
product_type | sum |
---|---|
衣服 | 5000 |
办公用品 | 600 |
厨房用具 | 11180 |
如果想要获得那样的结果,通常的做法是分别计算出合计行和按照商品种类进行汇总的结果,然后通过 UNION ALL
连接在一起(代码清单8-11)。
SELECT '合计' AS product_type, SUM(sale_price)
FROM Product
UNION ALL
SELECT product_type, SUM(sale_price)
FROM Product
GROUP BY product_type;
product_type | sum |
---|---|
合计 | 16780 |
衣服 | 5000 |
办公用品 | 600 |
厨房用具 | 11180 |
这样一来,为了得到想要的结果,需要执行两次几乎相同的SELECT语句,再将其结果进行连接,不但看上去十分繁琐,而且DBMS内部的处理成本也非常高,难道没有更合适的实现方法了吗?
ROLLUP——同时得出合计和小计
ROLLUP的中文翻译是
汇总,卷曲
,比如卷起百叶窗、窗帘卷,等等。其名称也形象地说明了该操作能够得到像从小计到合计这样,从最小的聚合级开始,聚合单位逐渐扩大的结果。
GROUPING运算符包含以下3种
ROLLUP
CUBE
GROUPING SETS
该运算符的作用,一言以蔽之,就是“一次计算出不同聚合键组合的结果”
SELECT product_type, SUM(sale_price) AS sum_price
FROM Product
GROUP BY ROLLUP(product_type);
■将“登记日期”添加到聚合键当中
在GROUP BY中添加“登记日期”(不使用ROLLUP)
SELECT product_type, regist_date, SUM(sale_price) AS sum_price
FROM Product
GROUP BY product_type, regist_date;
在GROUP BY中添加“登记日期”(使用ROLLUP)
SELECT product_type, regist_date, SUM(sale_price) AS sum_price
FROM Product
GROUP BY ROLLUP(product_type, regist_date);
GROUPING函数——让NULL更加容易分辨
之前使用ROLLUP所得到的结果有些蹊跷,问题就出在“衣服”的分组之中,有两条记录的regist_date列为NULL,但其原因却并不相同。
为了避免混淆,SQL提供了一个用来判断超级分组记录
(合计行记录)的NULL的特定函数—— 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 ROLLUP(product_type, regist_date);
这样就能分辨超级分组记录中的NULL和原始数据本身的NULL了。
在超级分组记录的键值中插入恰当的字符串
SELECT CASE WHEN GROUPING(product_type) = 1 THEN '商品种类 合计'
ELSE product_type
END AS product_type,
CASE WHEN GROUPING(regist_date) = 1 THEN '登记日期 合计'
ELSE CAST(regist_date AS NVARCHAR(16))
END AS regist_date, SUM(sale_price) AS sum_price
FROM Product
GROUP BY ROLLUP(product_type, regist_date);
CUBE——用数据来搭积木
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 CAST(regist_date AS NVARCHAR(16))
END AS regist_date, SUM(sale_price) AS sum_price
FROM Product
GROUP BY cube(product_type, regist_date)
在ROLLUP的基础上多出几条,多出来的记录就是只把regist_date
作为聚合键所得到的汇总结果。
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 CAST(regist_date AS NVARCHAR(16))
END AS regist_date, SUM(sale_price) AS sum_price
FROM Product
GROUP BY ROLLUP(product_type, regist_date)
UNION
SELECT '商品种类 合计',CAST(regist_date AS VARCHAR(16)),SUM(sale_price) FROM Product
GROUP BY regist_date
所谓CUBE,就是将GROUP BY子句中聚合键的“所有可能的组合”的汇总结果集中到一个结果中。因此,组合的个数就是2n(n是聚合键的个数)。本例中聚合键有2个,所以22 = 4。如果再添加1个变为3个聚合键的话,就是23 = 8
GROUPING SETS——取得期望的积木
如果希望从中选取出将“商品种类”和“登记日期”各自作为聚合键的结果,或者不想得到“合计记录和使用2个聚合键的记录”时,可以使用GROUPING SETS
SELECT CASE WHEN GROUPING(product_type) = 1
THEN '商品种类 合计'
ELSE product_type END AS product_type,
CASE WHEN GROUPING(regist_date) = 1
THEN '登记日期 合计'
ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date,
SUM(sale_price) AS sum_price
FROM Product
GROUP BY GROUPING SETS (product_type, regist_date);
上述结果中也没有全体的合计行(16780日 元 )。 与ROLLUP或者CUBE能够得到规定的结果相对,GROUPING SETS用于从中取出个别条件对应的不固定的结果。