MySQL必知必会13:临时表-保存中间结果

阅读整理自《MySQL 必知必会》- 朱晓峰,详细内容请登录 极客时间 官网购买专栏。

临时表

当遇到一些复杂查询的时候,经常无法一步到位,或者是一步到位会导致查询语句太过复杂,开发和维护的成本过高。这个时候,就可以使用临时表。

临时表是一种特殊的表,用来存储查询的中间结果,并且会随着当前连接的结束而自动删除。

MySQL 中有 2 种临时表,分别是内部临时表和外部临时表:

  • 内部临时表主要用于性能优化,由系统自动产生,我们无法看到;
  • 外部临时表通过 SQL 语句创建,我们可以使用;

创建临时表:

create temporary table 表名
(
字段名 字段类型,
...
);

跟普通表相比,临时表有 3 个不同的特征:

  • 临时表的创建语法需要用到关键字 temporary;
  • 临时表创建完成之后,只有当前连接可见,其他连接是看不到的,具有连接隔离性;
  • 临时表在当前连接结束之后,会被自动删除;

因为临时表有连接隔离性,不同连接创建相同名称的临时表也不会产生冲突,适合并发程序的运行。而且,连接结束之后,临时表会自动删除,也不用担心大量无用的中间数据会残留在数据库中。因此,可以利用这些特点,用临时表来存储 SQL 查询的中间结果。


优化复杂查询

需求:超市经营者想要查询 2020 年 12 月的一些特定商品销售数量、进货数量、返厂数量,那么,我们就要先把销售、进货、返厂这 3 个模块分开计算,用临时表来存储中间计算的结果,最后合并在一起,形成超市经营者想要的结果集。

数据准备:

mysql> select * from demo.transactiondetails;
+---------------+------------+----------+-------+------------+----------+
| transactionid | itemnumber | quantity | price | salesvalue | discount |
+---------------+------------+----------+-------+------------+----------+
|             1 |          1 |        2 | 89.00 |     176.22 |     0.99 |
|             1 |          2 |        5 |  5.00 |      24.75 |     0.99 |
|             2 |          1 |        3 | 89.00 |     234.96 |     0.88 |
+---------------+------------+----------+-------+------------+----------+

mysql> select * from demo.importhead;
+------------+------------+---------+------------+---------------------+
| listnumber | supplierid | stockid | operatorid | confirmationdate    |
+------------+------------+---------+------------+---------------------+
| 4587       |          1 |       1 |          1 | 2020-12-02 00:00:00 |
| 4588       |          2 |       1 |          1 | 2020-12-03 00:00:00 |
+------------+------------+---------+------------+---------------------+

mysql> select * from demo.importdetails;
+------------+------------+----------+-------------+-------------+
| listnumber | itemnumber | quantity | importprice | importvalue |
+------------+------------+----------+-------------+-------------+
| 4587       |          1 |        2 |          55 |         110 |
| 4587       |          2 |        5 |           3 |          15 |
| 4587       |          3 |        8 |           5 |          40 |
| 4588       |          1 |        3 |          60 |         180 |
+------------+------------+----------+-------------+-------------+

mysql> select * from demo.returnhead;
+------------+------------+---------+------------+---------------------+
| listnumber | supplierid | stockid | operatorid | confirmationdate    |
+------------+------------+---------+------------+---------------------+
| 654        |          1 |       1 |          1 | 2020-12-02 00:00:00 |
| 655        |          2 |       1 |          1 | 2020-12-03 00:00:00 |
+------------+------------+---------+------------+---------------------+

mysql> select * from demo.returndetails;
+------------+------------+----------+-------------+-------------+
| listnumber | itemnumber | quantity | returnprice | returnvalue |
+------------+------------+----------+-------------+-------------+
| 654        |          1 |        1 |          55 |          55 |
| 654        |          2 |        1 |           3 |           3 |
| 655        |          3 |        1 |           5 |           5 |
| 655        |          1 |        1 |          60 |          60 |
+------------+------------+----------+-------------+-------------+

mysql> select * from demo.goodsmaster;
+------------+---------+-----------+---------------+------+-----------+
| itemnumber | barcode | goodsname | specification | unit | saleprice |
+------------+---------+-----------+---------------+------+-----------+
|          1 | 0001    || 16||     89.00 |
|          2 | 0002    || NULL          ||      5.00 |
+------------+---------+-----------+---------------+------+-----------+

查询 transactiondetails 出每个单品的销售数量和销售金额,并存入临时表:

mysql> create temporary table demo.tempsales
    -> select itemnumber, sum(quantity) as quantity, sum(salesvalue) as salesvalue
    -> from demo.transactiondetails
    -> group by itemnumber
    -> order by itemnumber;
Query OK, 2 rows affected (0.03 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> select * from demo.tempsales;
+------------+----------+------------+
| itemnumber | quantity | salesvalue |
+------------+----------+------------+
|          1 |        6 |     495.73 |
|          2 |       21 |      98.65 |
+------------+----------+------------+

查询 importhead 进货数据,保存在临时表:

mysql> create temporary table demo.tempimport
    -> select b.itemnumber, sum(b.quantity) as quantity, sum(b.importvalue) as importvalue
    -> from demo.importhead a
    -> join demo.importdetails b
    -> on (a.listnumber = b.listnumber)
    -> group by b.itemnumber;
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from demo.tempimport;
+------------+----------+-------------+
| itemnumber | quantity | importvalue |
+------------+----------+-------------+
|          1 |        5 |         290 |
|          2 |        5 |          15 |
|          3 |        8 |          40 |
+------------+----------+-------------+

计算返厂信息 returndetails ,并且保存到临时表中:

mysql> create temporary table demo.tempreturn
    -> select b.itemnumber, sum(b.quantity) as quantity, sum(b.returnvalue) as returnvalue
    -> from demo.returnhead a
    -> join demo.returndetails b on (a.listnumber=b.listnumber)
    -> group by b.itemnumber;
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from demo.tempreturn;
+------------+----------+-------------+
| itemnumber | quantity | returnvalue |
+------------+----------+-------------+
|          1 |        2 |         115 |
|          2 |        1 |           3 |
|          3 |        1 |           5 |
+------------+----------+-------------+

现在,就可以把单品的销售信息、进货信息和返厂信息汇总到一起了。

引入商品信息表。因为商品信息表包含所有的商品,因此,把商品信息表放在左边,与其他的表进行左连接,就可以确保所有的商品都包含在结果集中。凡是不存在的数值,都设置为 0,然后再筛选一下,把销售、进货、返厂都是 0 的商品去掉,这样就能得到我们最终希望的查询结果:2020 年 12 月的商品销售数量、进货数量和返厂数量。

select a.itemnumber, a.goodsname, 
ifnull(b.quantity, 0) as salesquantity, 
ifnull(c.quantity, 0) as importquantity,
ifnull(d.quantity, 0) as returnquantity
from demo.goodsmaster as a
left join demo.tempsales as b on (a.itemnumber = b.itemnumber)
left join demo.tempimport as c on (a.itemnumber = c.itemnumber)
left join demo.tempreturn as d on (a.itemnumber = d.itemnumber)
having salesquantity>0 or importquantity>0 or returnquantity>0; -- 在结果集中剔除没有销售,没有进货,也没有返厂的商品
+------------+-----------+---------------+----------------+----------------+
| itemnumber | goodsname | salesquantity | importquantity | returnquantity |
+------------+-----------+---------------+----------------+----------------+
|          1 ||             6 |              5 |              2 |
|          2 ||            21 |              5 |              1 |
+------------+-----------+---------------+----------------+----------------+

总之,通过临时表,可以把一个复杂的问题拆分成很多个前后关联的步骤,把中间的运行结果存储起来,用于之后的查询。这样一来,就把面向集合的 SQL 查询变成了面向过程的编程模式,大大降低了难度。


内存临时表和磁盘临时表

由于采用的存储方式不同,临时表也可分为内存临时表磁盘临时表,它们有着各自的优缺点。

关于内存临时表,有一点需要注意的是,可以通过指定引擎类型(比如 engine=memory),来告诉 MySQL 临时表存储在内存中。

在磁盘上创建临时表时,只要不指定存储引擎,MySQL 会默认存储引擎是 InnoDB,并且把表存放在磁盘上。

创建一个内存中的临时表:

mysql> create tempory table demo.mytrans_memory
-> (
-> itemnumber int,
-> groupnumber int,
-> branchnumber int
-> ) engin = memory;  -- 临时表数据存在内存中
Query OK, 0 rows affected (0.00 sec)

在磁盘上创建一个同样结构的临时表:

mysql> create temporary table demo.mytrans_disk
-> (
-> itemnumber int,
-> groupnumber int,
-> branchnumber int
-> );
Query OK, 0 rows affected (0.00 sec)

向两张表里都插入同样数量的记录,然后再分别做一个查询:

mysql> select count(*) from demo.mytrans_memory;
+----------+
| count(*) |
+----------+
| 4355 |
+----------+
1 row in set (0.00 sec)
 
mysql> select count(*) from demo.mytrans_disk;
+----------+
| count(*) |
+----------+
| 4355 |
+----------+
1 row in set (0.21 sec)

显然,内存中的临时表查询速度更快。

类型 优点 缺点
内存临时表 查询速度快 一旦断电,全部丢失,数据无法找回
磁盘临时表 数据不易丢失 速度相对较慢

当然,临时表也有不足,比如会挤占空间。在使用临时表的时候,要从简化查询和挤占资源两个方面综合考虑,既不能过度加重系统的负担,同时又能够通过存储中间结果,最大限度地简化查询。


需求:

有这样的一个销售流水表,假设有多个门店,每个门店有多台收款机,每台收款机销售多种商品,请问如何查询每个门店、每台收款机的销售金额占所属门店的销售金额的比率呢?

mysql> select * from demo.task_sales;
+--------------+---------------+------------+------------+
| branchnumber | cashiernumber | itemnumber | salesvalue |
+--------------+---------------+------------+------------+
|            1 |             1 |          1 |         10 |
|            1 |             2 |          3 |         20 |
|            2 |             1 |          1 |         30 |
|            2 |             2 |          2 |         40 |
+--------------+---------------+------------+------------+
  1. 计算门店销售合计

    mysql> create temporary table demo.temp_branch
        -> select branchnumber, sum(salesvalue) as salesvalue
        -> from demo.task_sales
        -> group by branchnumber;
    Query OK, 2 rows affected (0.01 sec)
    Records: 2  Duplicates: 0  Warnings: 0
    
    mysql> select * from demo.temp_branch;
    +--------------+------------+
    | branchnumber | salesvalue |
    +--------------+------------+
    |            1 |         30 |
    |            2 |         70 |
    +--------------+------------+
    
  2. 按照门店、收款机,计算合计

    mysql> create temporary table demo.temp_cashier
        -> select branchnumber, cashiernumber, sum(salesvalue) as salesvalue
        -> from demo.task_sales
        -> group by branchnumber, cashiernumber;
    Query OK, 4 rows affected (0.01 sec)
    Records: 4  Duplicates: 0  Warnings: 0
    
    mysql> select * from demo.temp_cashier;
    +--------------+---------------+------------+
    | branchnumber | cashiernumber | salesvalue |
    +--------------+---------------+------------+
    |            1 |             1 |         10 |
    |            1 |             2 |         20 |
    |            2 |             1 |         30 |
    |            2 |             2 |         40 |
    +--------------+---------------+------------+
    
  3. 按照门店、收款机的销售占比

    mysql> select a.branchnumber, a.cashiernumber, a.salesvalue/b.salesvalue
        -> from demo.temp_cashier as a, demo.temp_branch as b
        -> where (a.branchnumber = b.branchnumber)
        -> order by a.branchnumber, a.cashiernumber;
    +--------------+---------------+---------------------------+
    | branchnumber | cashiernumber | a.salesvalue/b.salesvalue |
    +--------------+---------------+---------------------------+
    |            1 |             1 |                    0.3333 |
    |            1 |             2 |                    0.6667 |
    |            2 |             1 |                    0.4286 |
    |            2 |             2 |                    0.5714 |
    +--------------+---------------+---------------------------+
    4 rows in set (0.00 sec)
    

猜你喜欢

转载自blog.csdn.net/qq_31362767/article/details/123603051