1. 派生表的简介
派生表
是在外部查询的FROM子句中定义的
,只要外部查询一结束,派生表也就不存在了。派生表类似于临时表,但是在SELECT语句中使用派生表比临时表简单得多,因为它不需要创建临时表的步骤。
- 派生表可以简化查询,避免使用临时表。相比手动生成临时表性能更优越。
派生表和临时表的区别:
- 当主查询中包含派生表,或者当select 语句中包含union字句,或者当select语句中包含一个字段的order by 子句(对另一个字段的group by 子句)时,MySQL为了完成查询,则需要自动创建临时表存储临时结果集,这种临时表由MySQL自行创建,自行维护,成为自动创建的临时表。对于自动创建的临时表,由于内存临时表的性能更为优越,mysql总是首先使用内存临时表,而当内存临时表变得太大时,达到某个阈值的时候,内存临时表就转存为外存临时表。也就是说,外存临时表是内存临时表在存储空间上的一种延伸。内存临时表转存为外存临时表的阈值由系统变量max_heap_table_size和tmp_table_size的较小值决定。
派生表
和子查询
通常可互换使用。当SELECT语句的FROM子句中使用独立子查询
时,我们将其称为派生表(易百教程)。
以下说明了使用派生表的查询:
请注意,独立子查询是一个子查询,可独立于包含该语句的执行语句。
与子查询不同,
派生表必须具有别名
,以便稍后在查询中引用其名称。 如果派生表没有别名,MySQL将发出以下错误:Every derived table must have its own alias.
- 以下是使用派生表的简单例子:
SELECT
column_list
FROM
(SELECT
column_list
FROM
table_1) derived_table_name
WHERE derived_table_name.c1 > 0;
- 派生表使用规则
- 所有列必须要有名称
- 列名称必须是要唯一
- 不允许使用ORDER BY(除非指定了LIMIT)
2. 简单的派生表示例
以下查询从示例数据库(yiibaidb)中的orders表和orderdetails表中获得2013年销售收入最高的前5名产品。
SELECT
productCode,
ROUND(SUM(quantityOrdered * priceEach)) AS sales
FROM
orderdetails
INNER JOIN
orders USING (orderNumber)
WHERE
YEAR(shippedDate) = 2013
GROUP BY productCode
ORDER BY sales DESC
LIMIT 5;
+-------------+--------+
| productCode | sales |
+-------------+--------+
| S18_3232 | 103480 |
| S10_1949 | 67985 |
| S12_1108 | 59852 |
| S12_3891 | 57403 |
| S12_1099 | 56462 |
+-------------+--------+
5 rows in set
您可以使用此查询的结果作为派生表,并将其与products表相关联,products表的结构如下所示:
mysql> desc products;
+--------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+---------------+------+-----+---------+-------+
| productCode | varchar(15) | NO | PRI | | |
| productName | varchar(70) | NO | | NULL | |
| productLine | varchar(50) | NO | MUL | NULL | |
| productScale | varchar(10) | NO | | NULL | |
| productVendor | varchar(50) | NO | | NULL | |
| productDescription | text | NO | | NULL | |
| quantityInStock | smallint(6) | NO | | NULL | |
| buyPrice | decimal(10,2) | NO | | NULL | |
| MSRP | decimal(10,2) | NO | | NULL | |
+--------------------+---------------+------+-----+---------+-------+
9 rows in set
参考以下查询语句
SELECT
productName, sales
FROM
(SELECT
productCode,
ROUND(SUM(quantityOrdered * priceEach)) sales
FROM
orderdetails
INNER JOIN orders USING (orderNumber)
WHERE
YEAR(shippedDate) = 2013
GROUP BY productCode
ORDER BY sales DESC
LIMIT 5) AS top5products2013
INNER JOIN
products USING (productCode);
执行上面查询语句,得到以下结果
+-----------------------------+--------+
| productName | sales |
+-----------------------------+--------+
| 1992 Ferrari 360 Spider red | 103480 |
| 1952 Alpine Renault 1300 | 67985 |
| 2001 Ferrari Enzo | 59852 |
| 1969 Ford Falcon | 57403 |
| 1968 Ford Mustang | 56462 |
+-----------------------------+--------+
5 rows in set
在上面这个例子中:
- 首先,执行子查询来创建一个
结果集
或派生表
。 - 然后,在productCode列上使用products表连接top5product2013派生表的外部查询。
3. 派生表的使用
3.1 分配列别名(建议使用内嵌的别名)
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM (SELECT YEAR(orderdate), custid
FROM Sales.Orders) AS D(orderyear, custid)
# D为派生表名,括号中是派生表的列名,必须同派生表的列数据量一致;
# 也可以指定派生表列名,使用内嵌的列名
GROUP BY orderyear;
3.2 使用参数
DECLARE @empid AS INT = 3;
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM (SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders
WHERE empid = @empid) AS D
GROUP BY orderyear;
3.3 嵌套派生表
如果须要用一个本身就引用了某个派生表的查询去定义另一个派生表,最终得到的就是嵌套派生表。
例子:查询每年处理客户数超过70的订单年度和每年所处理的客户数量。
# 方案一:我们用第一节中单表查询查询出结果
SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts
FROM Sales.Orders
GROUP BY YEAR(orderdate)
HAVING COUNT(DISTINCT custid) > 70;
# 方案二:嵌套派生表
SELECT orderyear, numcusts
FROM (SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM
(SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders) AS D1
GROUP BY orderyear
) AS D2
WHERE numcusts > 70;
3.4 多个引用
SELECT Cur.orderyear,
Cur.numcusts AS curnumcusts, Prv.numcusts AS prvnumcusts,
Cur.numcusts - Prv.numcusts AS growth
FROM (SELECT YEAR(orderdate) AS orderyear,
COUNT(DISTINCT custid) AS numcusts
FROM Sales.Orders
GROUP BY YEAR(orderdate)) AS Cur
LEFT OUTER JOIN
(SELECT YEAR(orderdate) AS orderyear,
COUNT(DISTINCT custid) AS numcusts
FROM Sales.Orders
GROUP BY YEAR(orderdate)) AS Prv
ON Cur.orderyear = Prv.orderyear + 1;
缺点:我们看到其实我们是对同一张表进行联接,但是需要将代码定义多遍,这无疑造成代码的混乱和冗余。
3. 一个更复杂的MySQL派生表示例
假设必须将2013年的客户分为3组:铂金,白金和白银。 此外,需要了解每个组中的客户数量,具体情况如下:
- 订单总额大于100000的为铂金客户;
- 订单总额为10000至100000的为黄金客户;
- 订单总额为小于10000的为银牌客户
要构建此查询,首先,您需要使用CASE
表达式和GROUP BY
子句将每个客户放入相应的分组中,如下所示:
SELECT
customerNumber,
ROUND(SUM(quantityOrdered * priceEach)) AS sales,
(CASE
WHEN SUM(quantityOrdered * priceEach) < 10000 THEN 'Silver'
WHEN SUM(quantityOrdered * priceEach) BETWEEN 10000 AND 100000 THEN 'Gold'
WHEN SUM(quantityOrdered * priceEach) > 100000 THEN 'Platinum'
END) AS customerGroup
FROM
orderdetails
INNER JOIN
orders USING (orderNumber)
WHERE
YEAR(shippedDate) = 2013
GROUP BY customerNumber
ORDER BY sales DESC;
以下是查询的输出:
+----------------+--------+---------------+
| customerNumber | sales | customerGroup |
+----------------+--------+---------------+
| 141 | 189840 | Platinum |
| 124 | 167783 | Platinum |
| 148 | 150123 | Platinum |
| 151 | 117635 | Platinum |
| 320 | 93565 | Gold |
| 278 | 89876 | Gold |
| 161 | 89419 | Gold |
| ************此处省略了一大波数据 *********|
| 219 | 4466 | Silver |
| 323 | 2880 | Silver |
| 381 | 2756 | Silver |
+----------------+--------+---------------+
然后,可以使用此查询作为派生表,并按如下所示进行分组:
SELECT
customerGroup,
COUNT(cg.customerGroup) AS groupCount
FROM
(SELECT
customerNumber,
ROUND(SUM(quantityOrdered * priceEach)) AS sales,
(CASE
WHEN SUM(quantityOrdered * priceEach) < 10000 THEN 'Silver'
WHEN SUM(quantityOrdered * priceEach) BETWEEN 10000 AND 100000 THEN 'Gold'
WHEN SUM(quantityOrdered * priceEach) > 100000 THEN 'Platinum'
END) AS customerGroup
FROM
orderdetails
INNER JOIN orders USING (orderNumber)
WHERE
YEAR(shippedDate) = 2013
GROUP BY customerNumber) AS cg
GROUP BY cg.customerGroup;
执行上面查询语句,得到以下结果
+---------------+------------+
| customerGroup | groupCount |
+---------------+------------+
| Gold | 61 |
| Platinum | 4 |
| Silver | 8 |
+---------------+------------+
3 rows in set