您的当前位置:首页>全部文章>文章详情

【MySql】关于外键和连接, 如何做到关联查询?

CrazyPanda发表于:2023-12-08 17:54:50浏览:407次TAG:

文章目录


外键和连接:如何做关联查询?

在实际的数据库应用开发过程中,我们经常需要把2个或2个以上的表进行关联,以获取需要的数据。这是因为,为了提高存取效率,我们会把不同业务模块的信息分别存放在不同的表里面。

但是,从业务层面上看,我们需要完整全面的信息为经营决策提供数据支撑。

还是以超市项目为例,数据库里面的销售流水表一般只保存销售必需的信息,比如商品编号,数量, 价格,金额和会员卡号等。但是,在呈现给超市经营者的统计报表中,只包括这些信息是不够的,比如商品编号,会员卡号,这些数字经营者就看不懂。

因此,必须要从商品信息表中提取出商品信息,从会员表中提取出会员信息,这样才能形成一个完整的报表。这种把分散在多个不同的表里的数据查询出来的操作,就是多表查询。

不过,多表查询可不简单,我们需要建立起多个表之间的关联,然后才能去查询,同时还需要规避关联表查询中的常见错误。具体该怎么做呢,请接着看:

超市项目中的进货模块,有两个这样的数据表,分别是进货单头表(importhead)和进货单明细表(importdetailes),我们每天都需要对这两张表进行CRUD操作。

进货单头表记录的是整个进货单的总体信息:

ad7dd57f5efacbfbe8c6c2f3ebb80bd9.jpeg

进货单明细表记录的是每次进货的商品明细。一条进货单头数据记录,对应多条进货商品的明细数据,也就是所谓的一对多的关系,具体如下表所示:

ad7dd57f5efacbfbe8c6c2f3ebb80bd9.jpeg

现在我们需要查询一次进货的所有相关数据,包括进货单的总体信息和进货商品的明细,这样一来,我们就需要把2个表关联起来,该如何进行操作呢?

在MySQL中,为了把2个表关联起来,会用到2个重要功能,分别是外键(foreign key)和连接(join)。

外键需要在创建表的阶段就定义,连接可以通过相同意义的字段把2个表连接起来,用在查询阶段。

如何创建外键?

首先我们来了解一下什么是外键?

假如我们有2个表,分别是表A和表B,它们通过一个公共字段id发生关联关系,我们把这个关联关系叫做R。如果id在表A中是主键,那么表A就是这个关系R中的主表。相应的,表B就是这个关系中的从表,表B中的id,就是表B用来引用表A中数据的,叫外键。

所以,外键就是从表中用来引用主表中数据的那个公共字段。

为了方便理解,请看下图:

b5a77db0cdbd436188c23d52d8838748.png

在MySQL中,外键是通过外键约束来定义的。外键约束就是约束的一种,它必须在从表中定义,包括指明哪个是外键字段,以及外键字段所引用的主表中的主键字段是什么。

MySQL系统会根据外键约束的定义,监控对主表中数据的删除操作。如果发现要删除的主表记录,正在被从表中某条记录的外键字段所引用,MySQL就会提示错误,从而确保了关联数据不会缺失。

外键约束可以在创建表的时候定义,也可以通过修改表来定义。语法结构如下:

[CONSTRAINT <外键约束名称>] FOREIGN KEY 字段名
REFERENCES <主表名> 字段名

我们可以在创建表的时候定义外键约束:

CREATE TABLE 从表名
(
  字段名 类型,
  ...
-- 定义外键约束,指出外键字段和参照的主表字段
CONSTRAINT 外键约束名
FOREIGN KEY (字段名) REFERENCES 主表名 (字段名)
)

当然,我们也可以通过修改表来定义外键约束:

ALTER TABLE 从表名 ADD CONSTRAINT 约束名 FOREIGN KEY 字段名 REFERENCES 主表名 (字段名);

一般情况下,表与表的关联都是提前设计好了的,因此,会在创建表的时候就把外键约束定义好。不过,如果需要修改表的设计,比如添加新的字段,增加新的关联关系,但是没有预先定义外键约束,那么,就要用修改表的方式来补充定义。

下面,我们来看看怎么创建外键约束:

先创建主表demo.importhead:

create table demo.importhead
(
	listnumber int primary key,
  	supplierid int,
  	stocknumber int, 
  	importtype int,
  	importquantity decimal(10, 3),
  	importvalue decimal(10, 2),
  	recoder int,
  	recodingdate datetime
);

然后创建从表demo.importdetails,并且给它定义外键约束:

create table demo.importdetails
(
listnumber int,
  itemnumber int,
  quantity decimal(10,3),
  importprice decimal(10,2),
  importvalue decimal(10,2),
  -- 定义外键约束,指出外键字段和参照的主表字段
  constraint fk_importdetails_importhead
  foreign key (listnumber) references importhead(listnumber)
);

运行这个SQL语句,我们就在创建表的同时定义了一个名字叫"fk_importdetails_importhead"的外键约束。同时,我们声明,这个外键约束的字段"listnumber"引用的是表importhead里面的字段"listnumber"

我们可以通过MySQL自带的、用于存储系统信息的数据库:information_schema来查看外键约束的相关信息:

mysql> SELECT
    ->     constraint_name, -- 表示外键约束名称
    ->     table_name, -- 表示外键约束所属数据表的名称
    ->     column_name, -- 表示外键约束的字段名称
    ->     referenced_table_name, -- 表示外键约束所参照的数据表名称
    ->     referenced_column_name -- 表示外键约束所参照的字段名称
    -> FROM
    ->     information_schema.KEY_COLUMN_USAGE
    -> WHERE
    ->     constraint_name = 'fk_importdetails_importhead';
+-----------------------------+---------------+-------------+-----------------------+------------------------+
| CONSTRAINT_NAME             | TABLE_NAME    | COLUMN_NAME | REFERENCED_TABLE_NAME | REFERENCED_COLUMN_NAME |
+-----------------------------+---------------+-------------+-----------------------+------------------------+
| fk_importdetails_importhead | importdetails | listnumber  | importhead            | listnumber             |
+-----------------------------+---------------+-------------+-----------------------+------------------------+
1 row in set (0.05 sec)

通过查询,我们可以看到,外键约束所在的表是“importdetails”,外键字段是“listnumber”,参照的主表是“importhead”,参照的主表字段是“listnumber”。这样,通过定义外键约束,我们已经建立起了 2 个表之间的关联关系。

关联关系建立起来之后,如何才能获取我们需要的数据呢?这时,我们就需要用到连接查询了。

连接

在MySQL中,有2种类型的连接,分别是内连接(inner join)和外连接(outer join)。

  • 内连接表示查询结果只返回符合连接条件的记录,这种连接方式比较常用;

  • 外连接则不同,表示查询结果返回某一个表中的所有记录,以及另一个表中满足连接条件的记录。

下面我们来认识一下内连接:

在MySQL中,关键字join,inner join,cross join的含义是一样的,都表示内连接。我们通过join把两个表关联起来,来查询两个表中的数据。

超市的项目中有会员销售的需求,所以我们的流水表中的数据记录,既包括非会员的普通销售,又包括会员销售。它们的区别是会员销售的数据记录包括会员编号,而在非会员销售的数据记录中,会员编号为空。

下面是销售表demo.trans,实际的销售表比较复杂,为了方便理解,对表进行了简化,并且假设业务字段cardno是会员信息表的主键,简化以后的结构如下:

ad7dd57f5efacbfbe8c6c2f3ebb80bd9.jpeg

再看一下简化后的会员信息表:

ad7dd57f5efacbfbe8c6c2f3ebb80bd9.jpeg

这两个表之间存在关联关系,表demo.trans中的字段cardno是这个关联关系中的外键。

我们可以通过内连接,查询所有会员销售的流水记录:

select 
	a.transactionno,
	a.itemnumber,
	a.quantity,
	a.price,
	a.transdate,
	b.membername
from 
	demo.trans as a
join 
	demo.membermaster as b 
on (a.cardno = b.cardno);

我们通过公共字段cardno把两个表关联到一起,查询出了会员消费的数据。

在这里,关键字join和关键字on配对使用,意思是查询满足关联条件“demo.trans”表中cardno的值与demo.membermaster表中的cardio值相等的两个表中的所有记录。

上述内容讲解的是内连接,下面我们再说说外连接。

跟内连接只返回符合连接条件的数据记录不同的是,外连接还可以返回表中的所有记录,它包括两类,分别是左连接和右连接。

  • 左连接:一般简写成left join,返回左表中的所有数据记录,以及右表中符合连接条件的记录;

  • 右连接:一般简写成right join,返回右表中的所有数据记录,以及左表中符合连接条件的记录。

当我们需要查询全部流水信息的时候就会用到外连接,代码如下:

select 
	a.transactionno,
	a.itemnumber,
	a.quantity,
	a.price,
	a.transdate,
	b.membername
from demo.trans as a
left join demo.membermaster as b -- left join以demo.trans为主
on (a.cardno = b.cardno);

可以看到,我用到了 LEFT JOIN,意思是以表 demo.trans 中的数据记录为主,这个表中的数据记录要全部出现在结果集中,同时给出符合连接条件(a.cardno=b.cardno) 的表 demo.membermaster 中的字段 membername 的值。

我们也可以使用 RIGHT JOIN 实现同样的效果,代码如下:

select 
	a.transactionno.
	a.itemnumber,
	a.quantity,
	a.price,
	a.transdate,
	a.membername
from 
	demo.membermaster as b
right join 
	demo.joins as a -- right join, 顺序颠倒了,还是以demo.trans为主
on (a.cardno = b.cardno);

运行之后查看结果:

mysql> SELECT
    ->     a.transactionno,
    ->     a.itemnumber,
    ->     a.quantity,
    ->     a.price,
    ->     a.transdate,
    ->     b.membername
    -> FROM
    ->     demo.trans AS a
    ->         LEFT JOIN   -- 左连接
    ->     demo.membermaster AS b ON (a.cardno = b.cardno);
+---------------+------------+----------+-------+---------------------+------------+
| transactionno | itemnumber | quantity | price | transdate           | membername |
+---------------+------------+----------+-------+---------------------+------------+
|             1 |          1 |    1.000 | 89.00 | 2020-12-01 00:00:00 | 张三       |
|             2 |          2 |    1.000 | 12.00 | 2020-12-02 00:00:00 | NULL       |
+---------------+------------+----------+-------+---------------------+------------+
2 rows in set (0.00 sec)

mysql> SELECT
    ->     a.transactionno,
    ->     a.itemnumber,
    ->     a.quantity,
    ->     a.price,
    ->     a.transdate,
    ->     b.membername
    -> FROM
    ->     demo.membermaster AS b
    ->         RIGHT JOIN   -- 右连接
    ->     demo.trans AS a
    ->     ON (a.cardno = b.cardno);
+---------------+------------+----------+-------+---------------------+------------+
| transactionno | itemnumber | quantity | price | transdate           | membername |
+---------------+------------+----------+-------+---------------------+------------+
|             1 |          1 |    1.000 | 89.00 | 2020-12-01 00:00:00 | 张三       |
|             2 |          2 |    1.000 | 12.00 | 2020-12-02 00:00:00 | NULL       |
+---------------+------------+----------+-------+---------------------+------------+
2 rows in set (0.00 sec)

关联查询中的误区

有了连接,我们就可以进行两个表的关联查询了。有个问题:

关联查询必须在外键约束的基础上,才可以吗?

其实,在MySQL中,外键约束不是关联查询的必要条件。很多人往往在设计表的时候,觉得只要连接查询就可以搞定一切了,外键约束太麻烦,没有必要,这样想的话,就进入了一个误区。

还是以超市进货的例子为例,假设一次进货数据是这样的:供货商编号是1,进货仓库编号是1。我们进货的商品编号是1234,进货数量是1,进货价格是10,进货金额是10.

insert into demo.importhead
(
listnumber,
supplierid,
stocknumber,
)
values
(
1234,
1,
1
);

运行SQL,查看表中内容:

mysql> SELECT *
    -> FROM demo.importhead;
+------------+------------+-------------+------------+----------+-------------+-------------+
| listnumber | supplierid | stocknumber | importtype | quantity | importprice | importvalue |
+------------+------------+-------------+------------+----------+-------------+-------------+
|       1234 |          1 |           1 |          1 |     NULL |        NULL |        NULL |
+------------+------------+-------------+------------+----------+-------------+-------------+
1 row in set (0.00 sec)

可以看到,我们有了一个进货单头,单号是 1234,供货商是 1 号供货商,进货仓库是 1 号仓库。

接着,我们向进货单明细表中插入进货明细数据:

insert into demo.importdetails
(
listnumber,
itemnumber,
quantity,
importprice,
importvalue
)
values
(
1234,
1,
1,
10,
10
);

运行SQL,查看表中记录:

mysql> SELECT *
    -> FROM demo.importdetails;
+------------+------------+----------+-------------+-------------+
| listnumber | itemnumber | quantity | importprice | importvalue |
+------------+------------+----------+-------------+-------------+
|       1234 |          1 |    1.000 |       10.00 |       10.00 |
+------------+------------+----------+-------------+-------------+
1 row in set (0.00 sec)

这样,我们就有了 1234 号进货单的明细数据:进货商品是 1 号商品,进货数量是 1 个,进货价格是 10 元,进货金额是 10 元。

这个时候,如果我删除进货单头表的数据,就会出现只有明细、没有单头的数据缺失情况。我们来看看会发生什么:

delete from demo.importhead
where listnumbere = 1234;

运行这条SQL语句,MySQL会提示错误,因为数据删除违反了外键约束。MySQL阻止了数据不一致的情况出现。

还有一个问题是,我插入数据的顺序,为什么我要先插入进货单头表的数据,再插入进货单明细表的数据呢?如果我先插入数据到从表(进货单明细表),会导致MySQL找不到参照的主表信息,会提示错误,因为添加数据违反了外键约束。

所以,虽然我们不用外键约束,也可以进行关联查询,但是有了它,MySQL系统才会保护我们的数据,避免出现误删的情况,从而提高系统整体的可靠性。

外键约束,可以帮助我们确定从表中的外键字段与主表中的主键字段之间的引用关系,还可以确保从表中数据所引用的主表数据不会被删除,从而保证了 2 个表中数据的一致性。

为什么在 MySQL 里,没有外键约束也可以进行关联查询呢?

原因是外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以,MySQL 允许你不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。

所以我们要尽量养成在关联表中定义外键约束的习惯。不过,如果业务场景因为高并发等原因,无法承担外键约束的成本,也可以不定义外键约束,但是一定要在应用层面实现外键约束的逻辑功能,这样才能确保系统的正确可靠。


猜你喜欢

【MySql】mysql 将数据库中的所有表结构和数据 导入到另一个库(亲测有效)
一、问题描述在本地开发一个功能,需要结合具体数据测试代码是否正确,但服务器上的数据不可随便修改。此时则需要将服务器上某个数据库的表批量导入到本地、而后进行测试。将数据库db1中的所有表及数据(如table1、table2),批量导入到另一个数据库db2中。二、解决方案刚开始自己一直尝试用DataGrip、Navicat的sql/csv格式导入导出,总有错误或者遗漏,比如报错中止、50张表却只导入32张等,可能是操作方式有误或者数据格式有问题。也试过用sql语句,但仍失败。经查阅资料、尝试多次,终
发表于:2024-01-30 浏览:371 TAG:
【MySql】json字段内数据求和
首先,我们需要使用JSON_EXTRACT函数来解析JSON数据。假设我们的数据表名为data_table,JSON数据字段名为json_data,需要查询的字段为field_name。```sql SELECT&nbsp;JSON_EXTRACT(json_data,&nbsp;&#39;$.field_name&#39;)&nbsp;AS&nbsp;extracted_field FROM&nbsp;data_table;###&nbsp;步骤2:使用JSON函数提取数组内字段 如果字段是一
发表于:2024-09-18 浏览:210 TAG: #mysql
【MySQL】MYSQL数据库设计规范
&nbsp;&nbsp; &nbsp; 1、数据库命名规范&nbsp; &nbsp; &nbsp; &nbsp; 采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线&amp;#39;_&amp;#39;组成;&nbsp; &nbsp; &nbsp; &nbsp; 命名简洁明确(长度不能超过30个字符);&nbsp; &nbsp; &nbsp; &nbsp; 例如:user, stat, log, 也可以wifi_user, wifi_stat, wifi_log给数据库加个
发表于:2024-07-17 浏览:242 TAG:
【MySql】SELECT* 会导致查询效率低的原因
前言一、适合SELECT*的使用场景二、SELECT*会导致查询效率低的原因2.1、数据库引擎的查询流程2.2、SELECT*的实际执行过程2.3、使用SELECT*查询语句带来的不良影响三、优化查询...
发表于:2024-10-21 浏览:297 TAG: #mysql
【MySql】MySQL产生死锁的根本原因及解决方法
概念死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程一、 什么是死锁死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程.二、 死锁产生的四个必要条件互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只
发表于:2024-03-13 浏览:283 TAG:
【MySql】批量替换数据表中某字段的值
系统域名变更,需要把数据库存储的图片、文件等链接域名也替换掉,使用了文字替换的方法,在此记录一下:数据库:mysqlUPDATE&nbsp;user&nbsp;SET&nbsp;headimg&nbsp;=&nbsp;REPLACE(headimg,&nbsp;&#39;a.com&#39;,&nbsp;&#39;b.com&#39;);其中 user 是数据表,headimg是其中的字段,把headimg中&#39;a.com&#39;全部替换成&#39;b.com&#39;,headimg中
发表于:2024-04-02 浏览:328 TAG:
【MySQL】索引有哪些优缺点
索引是数据库中用于提高查询性能的重要工具,但它也有一些有点和确定,一下是索引的主要优缺点:优点:&nbsp;&nbsp;&nbsp;&nbsp;1. 加速查询速度最显而易见的有嗲你是加速select查询速度。通过使用索引,数据库引擎能够更快的定位和检索数据,特别是在大型数据集中。&nbsp;&nbsp;&nbsp;&nbsp;2. 排序性能提升索引可以提高排序操作的性能,因为数据库引擎可以按照索引顺序而不是表中实际物理存储顺序来执行排序。&nbsp;&nbsp;&nbsp;&nbsp;3. 唯一
发表于:2024-06-16 浏览:266 TAG:
【MySql】深入解析数据库索引
写在前面&nbsp;MySQL索引是数据库中一个关键的概念,它可以极大地提高查询性能,加快数据检索速度。但是,要充分发挥索引的作用,需要深入理解它们的工作原理和使用方式。在本文中,我们将深入解析MySQL索引,探讨它们的重要性、类型、创建、维护以及最佳实践。一、基础介绍&nbsp;1.1&nbsp;什么是索引?在数据库中,索引是一种数据结构,用于快速查找表中的数据。索引包含表中一列或多列的值,这些值按照一定的顺序进行排序,以便优化数据的检索速度。通过使用索引,数据库可以避免全表扫描,从而提高查询
发表于:2023-12-07 浏览:350 TAG:
【MySql】SQL boy的CRUD操作
文章目录增删查改:如何操作表中的数据?select|where|group by|havingfromorder bylimit插入数据记录插入查询结果添加数据删除数据修改数据查询数据增删查改:如何操作表中的数据?关于SQL的CRUD操作是很重要的,有些公司面试的时候会要求我们写一些SQL,我上次面试字节的时候就遇到了(手动狗头)。在我们的超市项目中,我们给用户设计好了一个数据库demo.goodsmaster,定义好了里面的字段以及各种约束,如下:mysql&gt;&nbsp;desc&amp;nbs
发表于:2023-12-08 浏览:403 TAG:
【MSQL】MATCH() AGAINST()全文搜索
这个问题是关于MySQL的全文搜索查询。MATCH() AGAINST() 是MySQL全文搜索的一部分。问题中的代码是在查询中使用MATCH() AGAINST(),但是使用方式有一些问题。首先,MATCH() 函数的参数应该是一个字段名,而不是一个字段值。其次,AGAINST 子句后面应该是要搜索的字符串,并且这个字符串需要被单引号包围。在布尔全文搜索模式下,你需要使用一些特殊的操作符来表示搜索的重要性或条件。例如,+表示一个词必须存在,而 - 表示一个词必须不存在。SELECT&nbsp;
发表于:2024-07-26 浏览:235 TAG: