C++学习(13)——数据库的使用(MySQL)

该文章前面会简单介绍MySQL的一些基本知识,如已了解,可以直接跳到C语言 MySQL API章节,C++有支持MySQL的库,但是仍然选择C语言库,主要目的是为了练习用C++对C代码进行封装。

文章目录

数据库简介

在实际的开发过程中,应用程序会有大量的数据需要持久地存储在磁盘之中,目前我们已知的持久化存储的方式就是采用文件,但是在磁盘物理的结构上,文件其实是一种顺序的字符集合。使用文件可以很方便地输入和输出数据,但是这些数据都是简单的字节流,不包含任何复杂数据结构,所以应用开发者需要花费大量的时间去解析文件的内容,并将其映射到内存的数据结构以供应用程序后续使用。除此之外,由于磁盘的顺序读写的速度要远远快于随机读写,所以为了访问位置靠后的数据,要消耗大量的时间(类似于链表的访问)。另外,文件系统的数据无法保证数据的安全性,应用程序没有办法在存储数据的时候检查其是否正确,而当部分数据损坏时,文件系统本身无法定位问题和设计修复方案。

数据库是"按照数据结构来组织、存储和管理数据的仓库"。它是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库具有以下特点:

  • 数据结构化:数据库系统实现了整体数据的结构化,数据之间具有联系。
  • 数据的共享性高,冗余度低,易扩充:数据是面向整体的,可以被多个用户、多个应用程序共享使用,大大减少了数据冗余,节约了存储空间。
  • 数据独立性高:数据独立性包括数据的物理独立性和逻辑独立性。物理独立性是指数据在磁盘上的存储方式由数据库管理系统(DBMS)管理,用户程序不需要了解;逻辑独立性是指用户的应用程序与数据库的逻辑结构是相互独立的,当数据的逻辑结构改变时,用户程序可以不需要改变。
  • 数据由DBMS统一管理和控制:DBMS必须提供数据的安全性保护、完整性检查、并发访问控制和故障恢复等功能。

数据库分类

数据库可以根据不同的分类标准划分为多种类型。当前的数据库可以大致分为两类:关系型数据库和非关系型数据库

  • 关系型数据库使用关系来组织数据库当中的各个数据,所谓的关系可以认为就是有若干行和列构成的表格。

    由于关系型数据库便于理解易于使用,所以长期以来是市场上的主流数据库模式。关系型数据库通常会采用一个名为SQL(Structured Query Language,结构化查询语言)的语言来访问和更新。现在市场占用率比较高的关系型数据库有这些:Oracle、MySQL、PostgreSQL、DB2、Informix、Sybase、SQL Server 2000、Access、SQLite。Oracle是商业数据库,性能优异但是收费昂贵,一般用于传统行业和银行领域;MySQL和PostgreSQL是开源的关系型数据库管理系统,被大多数互联网公司采用。

  • 非关系型数据库现在一般统称为NoSQL,由于在市场竞争中始终无法取代关系型数据库,现在一般将其解释为"Not only SQL",从而表示非关系型数据库是关系型数据的补充之义。NoSQL一般认为不适合作为通用领域的存储部件,但是在一些特定领域,比如高并发领域等等还是有一定的用武之地。常见的NoSQL产品有:Redis、Memcached、MongoDB、Cassandra等等。NoSQL的存储结构各有千秋,不过一般都会支持key-value的键值对存储结构。

我们使用的是目前数据库排行榜上排名第二的关系型数据库MySQL.

数据库排行榜的链接是: https://db-engines.com/en/ranking,大家可以自行查看。

MySQL发展历史

MySQL 是一款开源的关系型数据库管理系统 (RDBMS),其历史可以追溯到1994年。当时,两位瑞典工程师 Michael Widenius 和 David Axmark 在瑞典乌普萨拉大学的计算机科学系工作,他们开始着手于一个轻量级的数据库项目,命名为 mSQL(miniSQL)。

在经过几年的发展和改善后,Widenius 和 Axmark 开始寻求将他们的项目扩展为更为完整的数据库系统。1995 年,MySQL 1.0版正式发布,MySQL 取代了 mSQL 成为这个新项目的名称。MySQL 的目标是成为一个功能强大的数据库管理系统,能够处理大规模数据集,并支持多种应用程序。(MySQL这个名字据说是Widenius根据自己女儿的名字来命名的。)

MySQL 最初的版本只包含一些基本的功能,例如查询语言和表结构定义,但随着时间的推移,MySQL 逐渐成熟,并逐步扩展了许多实用和复杂的功能。其中,最令人印象深刻的改变之一是增加了支持存储过程和触发器的功能。

自 MySQL 首次发布以来,就一直是一款免费开源的数据库软件,用户可以根据自己的需要下载、安装和使用 MySQL。由于其可以跨多个平台运行(包括 Windows、Linux、Unix等),因此 MySQL 在各个行业中得到了广泛的应用。

2010 年 Oracle 公司收购了 MySQL AB 公司,这标志着 MySQL 成为了 Oracle 公司的子公司,MySQL 也成为了 Oracle 公司旗下的关键产品之一。通过 Oracle 公司的支持,MySQL 不断发展壮大,并成为了世界上最受欢迎的关系型数据库管理系统之一。

以下是MySQL发展的几个关键阶段

  1. 早期发展(1994-1999)

    • 1994年,Michael Widenius 和 David Axmark 开始开发 mSQL(miniSQL),这是 MySQL 的前身。
  • 1995年,发布MySQL1.0版本,mSQL正式更名为MySQL。
  • 1996年,MySQL 3.11.1发布,直接跳过了MySQL 2.x版本。
  • 1999年,MySQL AB 公司成立,专门负责 MySQL 的开发和商业支持。
  1. 开源时代(2000-2008)
    • 2000年,ISAM升级为MyISAM存储引擎,MySQL基于GPL协议开放源码。
    • MySQL 的开源模式吸引了大量的社区贡献者,他们通过提交补丁、修复错误和增加新功能来推动 MySQL 的发展。
    • 2002年,MySQL 4.0发布,集成了后来大名鼎鼎的InnoDB存储引擎。
    • 2005年,MySQL 5.0发布,开始支持游标、存储过程、触发器、视图、XA事务等特性。
    • 2008年,Sun Microsystems公司以10亿美金收购MySQL AB。同年,发布MySQL 5.1,开始支持定时器(Event scheduler)、分区、基于行的复制等特性。
  2. 所有权变更与继续发展(2009年至今)
    • 2009年,Oracle公司以74亿美金收购Sun公司,从而间接获得了MySQL的所有权。
    • 2010 年,MySQL 5.5 正式版发布,Oracle 完成了大量改进,并将 InnoDB 改成默认引擎。
    • 2018年,MySQL 8.0 发布,带来了许多重要的改进,包括事务日志持久性、窗口函数、更好的 JSON 支持和更高级的安全特性。

MySQL初步认识

当MySQL数据库系统安装完毕之后,会以守护进程的方式启动一个mysqld 服务器,默认端口是3306。

------ 3306端口是MySQL的默认客户端连接端口,33060是MySQL8.0+新增的X plugin端口(不用管)。

基本结构

以下是我们对于MySQL数据库产品的一个基本结构的认识:

与MySQL服务器的三种交互方式

我们可以使用三种方式来与MySQL服务器进行交互:

  • 命令行方式
  • 图形化界面方式
  • C语言API方式
    命令行方式

以下是通过命令行来连接MySQL服务器的操作:

cpp 复制代码
$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the c</urrent input statement.

mysql> 

ctrl + L实现清屏

输入quit,就可以退出与MySQL服务器的连接

mysql 复制代码
mysql>quit
Bye

图形化界面方式

其实图形化界面的客户端程序有多种,比如Navicat Premium、SQLyog等。如图为SQLyog界面:

在刚接触数据库时,我们更推荐使用命令行方式来学习,毕竟后续会涉及到大量的SQL语句的使用,在熟悉了这些语句后,再来使用图形化界面工具会更好。

C语言API方式

关系模型

在表的结构(关系模型)中,关系、元组、属性和域是核心概念,以下是它们各自的定义和相互之间的关系。

关系(Relation)

关系在数据库中表示为一个二维表,这个二维表由行和列组成。它实际上描述了数据之间的一种逻辑结构或联系。一个关系就是一个数据表,包含了多个元组(记录)和多个属性(字段)。

元组(Tuple)

在关系模型中,元组是关系(表)中的一行,代表了一个具体的实体或记录。元组是关系中的单个实例,由多个属性值组成。在二维表中,每一行就是一个元组。

属性(Attribute)

属性是关系(表)中的一列,代表了实体的某个特征或属性。每个属性都有一个特定的名称和数据类型,用于描述实体的某个方面。在二维表中,每一列就是一个属性。

域(Domain)

域是属性的取值范围,即属性可以取到的值的集合。它定义了属性的数据类型和可能的取值范围。例如,一个表示年龄的属性的域可能是0到150之间的整数。
关系、元组、属性和域之间的关系可以用以下方式描述:

  • 关系是由多个元组组成的,每个元组都是关系中的一个实例。

  • 每个元组由多个属性值组成,这些属性值对应于关系中的各个属性。

  • 每个属性都有一个特定的域,定义了该属性可以取到的值的范围。

简单来说,关系描述了一个数据表的结构,元组是表中的具体记录,属性是表中的列(即数据的特征),而域则定义了属性的取值范围。这四个概念共同构成了关系模型的基础。

SQL

SQL(Structured Query Language)即结构化查询语言,它是一种高级的非过程化编程语言,或者说是一种声明式语言,其最初的数学来源是关系代数和元组关系演算。它主要用于存取数据以及查询、更新和管理关系数据库系统。

所谓**声明式语言(Declarative Language)**是一种编程范式,它主要关注于描述程序所期望的结果,而不是详细地描述如何获取这个结果。在声明式语言中,程序员不需要明确地列出每一步的执行细节,而是描述出问题的性质,以及求解该问题所需的约束条件。声明式语言的主要特点包括:

  • 描述性:程序员通过描述问题的性质和约束条件来编写程序,而不是详细地列出执行步骤。
  • 自动执行:一旦声明了问题的性质和约束条件,系统会自动决定如何执行这些声明以得到结果。
  • 易于理解和维护:由于声明式语言更关注于问题的本质而不是具体的实现细节,因此它通常比命令式语言更容易理解和维护。
  • 抽象程度高。

与声明式语言相对的是命令式语言(Imperative Language),如C、C++、Java等,它们要求程序员明确地指定执行步骤和顺序。

由于SQL是一种声明式语言,SQL允许用户在高层数据结构上工作,而无需了解数据的具体存放方式。SQL具有以下主要特点:

  • 非过程化:用户只需要描述所需的数据,而不需要指定如何获取这些数据,具体的存取路径和操作过程由数据库管理系统自动完成。

  • 面向集合:SQL采用集合操作方式,一次可以处理多个数据项。

  • 语言简洁:SQL的语法接近英语口语,易于学习和使用。

  • 功能强大:SQL提供了丰富的数据处理功能,包括数据定义数据操纵数据查询数据控制等。
    接下来,我们重点看看SQL语言所包含的功能。SQL语言主要包含DDL、DML和DQL语言。

  • DDL ,即数据定义语言(Data Definition Language):通过CREATEALTER 和>DROP等语句,用于创建、修改和删除数据库、表、索引等数据库对象。

  • DML ,即数据操纵语言(Data Manipulation Language):通过INSERTUPDATEDELETE等语句,用于向表中添加数据、修改表中的数据以及从表中删除数据。

  • DQL ,即数据查询语言(Data Query Language):这是SQL的核心功能,通过SELECT语句,用户可以根据各种条件从数据库中检索数据。

数据库相关操作

查看数据库

当我们通过命令行连接到了MySQL数据库服务器后,想要查看有哪些数据库,可以使用如下语句

mysql 复制代码
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)

从上述结果观察,我们发现使用SQL语句,有以下特点:

  • 一个查询语句总是以分号结尾,换行并不意味着结束输入。
  • 一个查询语句的返回结果是以二维表的形式出现的,第一行会显示每一列的属性名。
  • 一个查询语句的返回结果还会告知结果的行数和执行时间,以便粗略的了解性能。

另外,SQL语句中的关键字是大小写无关的,以上语句也可以采用大写形式,其结果是相同的。

mysql 复制代码
mysql> SHOW DATABASES;

如果接下来想使用某一数据库,则可以使用如下语句:

mysql 复制代码
mysql> use mysql;

创建数据库

指令

sql 复制代码
create database 数据库名;

创建一个数据库

mysql客户端与服务端之间的通信依靠socket,本质上还是一种请求-响应的模型(客户端给服务器发请求,服务器给客户端响应)

创建数据库背后有一些细节值得关注(如下图,左侧是字符集右侧是字符校对集)

我们先来看看MySQL中创建数据库的语法规则:

sql 复制代码
CREATE DATABASE [IF NOT EXISTS] 数据库名
[CHARACTER SET 字符集]
[COLLATE 排序规则];

\[\]内的内容代表可选。
最简

sql 复制代码
CREATE  DATABASE [IF NOT EXISTS] db_name 

如果需要指定字符集和排序规则,可以使用

sql 复制代码
CREATE DATABASE [IF NOT EXISTS] db_name 
[DEFAULT] CHARACTER SET 字符集名称 
COLLATE  排序规则名称

如:

mysql 复制代码
mysql> CREATE DATABASE wangdao DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

字符集和字符校对集(了解)

查看字符集

查看MySQL中支持的字符集

mysql 复制代码
mysql> SHOW CHARACTER SET;
+----------+---------------------------------+---------------------+--------+
| Charset  | Description                     | Default collation   | Maxlen |
+----------+---------------------------------+---------------------+--------+
| armscii8 | ARMSCII-8 Armenian              | armscii8_general_ci |      1 |
| ascii    | US ASCII                        | ascii_general_ci    |      1 |
| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 |
| binary   | Binary pseudo charset           | binary              |      1 |
| cp1250   | Windows Central European        | cp1250_general_ci   |      1 |
| cp1251   | Windows Cyrillic                | cp1251_general_ci   |      1 |
| cp1256   | Windows Arabic                  | cp1256_general_ci   |      1 |
| cp1257   | Windows Baltic                  | cp1257_general_ci   |      1 |
| cp850    | DOS West European               | cp850_general_ci    |      1 |
| cp852    | DOS Central European            | cp852_general_ci    |      1 |
| cp866    | DOS Russian                     | cp866_general_ci    |      1 |
| cp932    | SJIS for Windows Japanese       | cp932_japanese_ci   |      2 |
| utf8mb4  | UTF-8 Unicode                   | utf8mb4_0900_ai_ci  |      4 |
+----------+---------------------------------+---------------------+--------+
查看字符校对集

查看字符校对集,也就是字符集排序规则

mysql 复制代码
mysql> SHOW COLLATION;
+-------------------------+----------+-----+---------+----------+---------+---------------+
| Collation               | Charset  | Id  | Default | Compiled | Sortlen | Pad_attribute |
+-------------------------+----------+-----+---------+----------+---------+---------------+
| armscii8_bin            | armscii8 |  64 |         | Yes      |       1 | PAD SPACE     |
| armscii8_general_ci     | armscii8 |  32 | Yes     | Yes      |       1 | PAD SPACE     |
| ascii_bin               | ascii    |  65 |         | Yes      |       1 | PAD SPACE     |
| ascii_general_ci        | ascii    |  11 | Yes     | Yes      |       1 | PAD SPACE     |
| big5_bin                | big5     |  84 |         | Yes      |       1 | PAD SPACE     |
| big5_chinese_ci         | big5     |   1 | Yes     | Yes      |       1 | PAD SPACE     |

注意:数据库的名字是大小写敏感的。

如何理解字符集与校对集?假设你手上有一本中文词典

  • 字符集 :决定了这本词典里收录了哪些汉字 (是简体字、繁体字,还是生僻字),以及每个字在计算机里用什么二进制编码存储。比如 utf8mb4 字符集,就包含了世界上绝大多数语言的文字符号。
  • 校对集 :决定了这本词典里汉字的排列顺序查找规则。是按拼音排?还是按部首笔画排?查找时,大小写是否区分?"a"和"A"算不算同一个字?
字符集utf8mb4

MySQL8.0默认使用的字符集是utf8mb4,该字符集包含了Emoji表情,即MySQL8.0可以存储Emoji表情。而MySQL5.7是无法存储Emoji表情的。

对于Emoji表情,它在Unicode中的编码是U+1F600,在存储时,这个编码会转换成一个或多个字节。以UTF8编码为例,它是一种可变长度的Unicode编码,它可以用1-4个字节来表示一个Unicode字符。由于Emoji表情属于Unicode中的辅助平面字符,因此它通常是需要4个字节来进行编码和存储的。

Emoji表情的链接: https://www.emojiall.com/zh-hans/all-emojis

字符校对集utf8mb4_0900_ai_ci

utf8mb4_0900_ai_ci 是 MySQL 8.0 之后数据库的一种排序规则(collation)。这个名称包含了几个关键部分,我们可以逐一解释它们:

  • utf8mb4:这是一个字符集(charset)的名称,用于存储 Unicode 字符。utf8mb4 是 utf8 的一个超集,支持存储四字节的 Unicode 字符,这意味着它可以存储更多的字符,包括某些表情符号和特殊字符。
  • 0900:这部分对应的是 Unicode 9.0 的规范。它表示这个排序规则是基于 Unicode 9.0 的标准来定义的。
  • ai:表示 "accent insensitivity",也就是"不区分音调"。这意味着在排序和比较时,字符的重音(或音调)将被忽略。
  • ci:表示 "case insensitivity",也就是"不区分大小写"。在排序和比较时,字符的大小写将被忽略。

utf8mb4_0900_ai_ci 排序规则的优点和原因包括:

  1. 支持更多的字符utf8mb4_0900_ai_ci 字符集支持存储和处理包括表情符号、特殊符号和罕见字符在内的更多 Unicode 字符。
  2. 更准确的排序方式:这个排序规则是根据 Unicode 标准制定的,它提供了更准确和一致的排序方式。

在 MySQL 8.0 之后的版本中,utf8mb4_0900_ai_ci 被设置为默认的排序规则,取代了之前版本的 utf8mb4_general_ci。这是因为 utf8mb4_0900_ai_ci 提供了更广泛的 Unicode 支持和更准确的排序方式。

然而,当将 MySQL 8.0 版本的表导入到 MySQL 5.7 或更低版本时,可能会遇到字符集无法识别的问题,因为这些旧版本可能不支持 utf8mb4_0900_ai_ci 排序规则。

修改数据库

当需要修改数据库的字符集和排序规则时,可以使用ALTER语句。

sql 复制代码
ALTER DATABASE 数据库名 DEFAULT CHARACTER SET 字符集名称 COLLATE 排序规则;

注意:如想要修改数据库的名字,是不支持直接修改的。

一般的做法是创建一个新库,然后将旧库中的所有表剪切到新的库中

sql 复制代码
RENAME TABLE 旧库名.student TO 新库名.student;

删除数据库

当需要删除数据库时,可以使用DROP语句。

sql 复制代码
DROP DATABASE 数据库名字;

这四个是系统数据库,不能删除!!!

  1. mysql(核心元数据库)

    存储数据库实例的运行时元数据,主要包括:用户账号与权限信息(user、db 等表)、时区、存储引擎配置及服务器日志等。该库是 MySQL 正常启动与身份认证的基础,不可删除或随意修改。

  2. information_schema(标准化信息视图)

    提供对数据库服务器元数据的标准化只读访问。包含所有数据库中的表、列、索引、约束等结构信息,基于 SQL 标准实现,是一组虚拟视图,无物理文件,不可修改。

  3. performance_schema(性能监控接口)

    用于低开销监控数据库运行时的性能指标。记录当前会话、锁等待、I/O 操作、内存使用、SQL 语句执行状态等实时数据,所有数据驻留于内存中,重启后重置,仅支持只读查询。

  4. sys(性能诊断辅助视图)

    提供对 performance_schema 和 information_schema 数据的优化组织形式。以可读性更高的视图、存储过程及函数形式,封装了常用性能诊断逻辑(如慢查询分析、锁等待检测),仅作为查询接口,不存储业务数据。

数据类型

在数据库中,数据类型是确保数据完整性、准确性、高效性和易用性的重要因素。

以下是常见的数据类型。

分类 数据类型 说明
数值类型 BIT(M) 位类型,M指定位数,范围1-64
BOOL,BOOLEAN 使用1或0表示真或假
TINYINT UNSIGNED ZEROFILL 1个字节。带符号的范围 -128 到127 无符号的范围0到255
SMALLINT UNSIGNED ZEROFILL 2个字节。 带符号的范围-32768到32767 无符号的范围0到65535
MEDIUMINT UNSIGNED ZEROFILL 3个字节。 带符号的范围-8388608到8388607 无符号的范围0到16777215
INTUNSIGNED ZEROFILL 4个字节。 带符号的范围-2147483648到2147483647 无符号的范围0到4294967295
BIGINT UNSIGNED ZEROFILL 8个字节。 带符号的范围-2^63到 2^63 - 1 无符号的范围0到2^64 - 1
FLOAT(M,D) 单精度浮点数,M指定显示长度,D指 定小数位数
DOUBLE(M,D) 双精度浮点数
文本、二进制类型 CHAR(size) 固定长度字符串,size表示有多少个字符
VARCHAR(size) 可变长度字符串,size表示最大有多少个字符
BLOB、LONGBLOB 二进制数据,比如图片、视频等
TEXT、LONGTEXT 文本字符串
日期和时间 DATE 日期(YYYY-MM-DD)
TIME 时间(HH:MM:SS)
DATETIME 日期和时间的组合
TIMESTAMP 时间戳

注意

  • 关于数据类型中,字符串CHAR和VARCHAR是最常用的,其他的比如BLOB和TEXT就不推荐使用了。需要存图片的话,直接存BLOB这种巨大的二进制文件会非常影响性能,一般是数据库存图片的路径,图片本身还是存在磁盘上。

表结构相关操作

在上面的演示中,我们已经创建了一个wangdao数据库,如果想知道该数据库中有哪些表

使用语句show tables:

mysql 复制代码
mysql> show tables;
+-------------------+
| Tables_in_wangdao |
+-------------------+
| char_type         |
| test_deadlock     |
+-------------------+
2 rows in set (0.00 sec)

创建表

在MySQL中,创建表的基本语法是CREATE TABLE语句,其语法规则如下:

sql 复制代码
CREATE TABLE 表名 (
	filed1 datatype,
	filed2 datatype,
	...,
	filedn datatype
);

如我们想要添加一张表student,可以使用如下语句:

mysql 复制代码
CREATE TABALE student(
	id INT,
	name VARCHAR(20),
	age INT,
	birth DATE, 
	chinese INT
);

注意:在创建表时,表名和属性是大小写敏感的。

囊括表中属性的是小括号,不是大括号!

查看表结构

所谓查看表结构,即查看表中有几个列或属性或字段,每个字段的数据类型。其查看的方式有两种:

  • DESC tb_name;
  • SHOW CREATE TABLE tb_name;
    比如我们想要查看student表结构,直接使用DESC语句
mysql 复制代码
mysql> DESC student;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| name    | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

Field 字段名称

Type 字段数据类型

Null 是否允许为 NULL(YES/NO)

Key 键类型(PRI 主键 / UNI 唯一键 / MUL 普通索引)

Default 字段默认值

Extra 额外属性

第二种方式,则是使用SHOW CREAT TABLE语句了。

mysql 复制代码
mysql> SHOW CREATE TABLE student;
+---------+--------------------------------------------------------+
| Table   | Create Table                                           |
+---------+--------------------------------------------------------+
| student | CREATE TABLE `student` (
`id` int DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`age` int DEFAULT NULL,
`birth` date DEFAULT NULL,
`chinese` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+---------+--------------------------------------------------------+
1 row in set (0.00 sec)

显示的内容更全一些,还包括了表的核心配置:存储引擎(InnoDB)、字符集、排序规则。

修改表结构

当创建了表结构之后,假设需要对其中的属性进行添加、修改和删除操作,则必须要使用ALTER TABLE语句了,其语法规则如下:

mysql 复制代码
# 添加属性
ALTER TABLE tb_name ADD field datatype [, field datatype];
# 修改属性名字
ALTER TABLE tb_name change old_field new_filed datatype;
# 修改属性的数据类型
ALTER TABLE tb_name MODIFY filed datatype;
# 删除属性
ALTER TABLE tb_name DROP filed;

给学生表添加属性math

mysql 复制代码
mysql> ALTER TABLE student ADD math INT;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC student;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| name    | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | int         | YES  |     | NULL    |       |
| math    | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

修改学生表中属性name的名字为s_name

mysql 复制代码
mysql> ALTER TABLE student CHANGE name s_name VARCHAR(20);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC student;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| s_name  | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | int         | YES  |     | NULL    |       |
| math    | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql> 

修改学生表中属性chinese的数据类型为float

mysql 复制代码
mysql> ALTER TABLE student MODIFY chinese FLOAT(5,2);
Query OK, 0 rows affected, 1 warning (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 1

mysql> DESC student;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| s_name  | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | float(5,2)  | YES  |     | NULL    |       |
| math    | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

删除学生表中的属性chinese

mysql 复制代码
mysql> ALTER TABLE student DROP chinese;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DESC student;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int         | YES  |     | NULL    |       |
| s_name | varchar(20) | YES  |     | NULL    |       |
| age    | int         | YES  |     | NULL    |       |
| birth  | date        | YES  |     | NULL    |       |
| math   | int         | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

数据操纵语言DML

DML (Data Manipulation Language) 是 SQL (Structured Query Language) 的一部分,用于处理数据库中的数据。与 DDL (Data Definition Language) 不同,DML 主要关注数据的插入、更新、删除和查询。

插入(添加)数据

往表中插入数据,使用INSERT语句,其语法规则:

cpp 复制代码
INSERT INTO tb_name [(field1, field2, ...)] VALUES(value1, value2, ...);

情况一:对指定列进行数据的添加

cpp 复制代码
# 一次添加1行数据
mysql> INSERT INTO student(name,age,birth,chinese) VALUES('dijia', 20, '2000-01-01', 80);
Query OK, 1 row affected (0.00 sec)

# 查看表中的数据情况
mysql> SELECT * FROM student;
+------+-------+------+------------+---------+------+
| id   | name  | age  | birth      | chinese | math |
+------+-------+------+------------+---------+------+
| NULL | dijia |   20 | 2000-01-01 |      80 | NULL |
+------+-------+------+------------+---------+------+
1 row in set (0.00 sec)

# 一次添加多行数据
mysql> INSERT INTO student(name,age,birth,chinese) 
	-> VALUES('taijia', 21, '2001-02-02', 90),
	-> ('leiou', 22, '2002-03-03', 70);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

# 查看表中的数据情况
mysql> SELECT * FROM student;
+------+--------+------+------------+---------+------+
| id   | name   | age  | birth      | chinese | math |
+------+--------+------+------------+---------+------+
| NULL | dijia  |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou  |   22 | 2002-03-03 |      70 | NULL |
+------+--------+------+------------+---------+------+
3 rows in set (0.00 sec)

情况二:对所有列进行数据的添加

这种情况依然可以采用情况一的方式来添加,不过由于是对所有列都添加数据,所以可以进行简化操作,INSERT语句中可以不再指定列了,但添加数据时,必须要将所有列的数据全部加上,否则会报错。

cpp 复制代码
mysql> DESC student;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| name    | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | int         | YES  |     | NULL    |       |
| math    | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

# 一次添加一行数据
mysql> INSERT INTO student VALUES(1, 'oubu', 22, '2003-04-04', 50, 55);
mysql> SELECT * FROM student;
+------+--------+------+------------+---------+------+
| id   | name   | age  | birth      | chinese | math |
+------+--------+------+------------+---------+------+
| NULL | dijia  |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou  |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu   |   22 | 2003-04-04 |      50 |   55 |
+------+--------+------+------------+---------+------+
4 rows in set (0.00 sec)

# 一次添加多行数据
mysql> INSERT INTO student VALUES(2, 'beisuosi', 24, '2004-05-05', 90, 95),
	-> (3, 'aguru', 26, '2005-06-06', 77, 66);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM student;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
| NULL | dijia    |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia   |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
6 rows in set (0.00 sec)

更新数据

更新表中的数据,使用UPDATE语句,其语法规则:

sql 复制代码
UPDATE tb_name SET field1=expr1 [, field2=expr2 ] [WHERE ];

如:将student表中名字为"dijia"的id更新为4

mysql 复制代码
mysql> UPDATE student SET id=4 WHERE name='dijia';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM student;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    4 | dijia    |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia   |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
6 rows in set (0.00 sec)

接下来,我们要演示删除数据了。但在删除数据之前,我们先复制一份数据作为备份,是更好的选择。所以我们先来看看如何复制一张表。

复制表

关于复制表,有两种情况需要考虑:

情况一:复制表结构,但并不复制数据

mysql 复制代码
CREATE TABLE new_tbname LIKE old_tbname;

如:

mysql 复制代码
mysql> CREATE TABLE student2 LIKE student;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM student2;
Empty set (0.00 sec)

mysql> DESC student2;
+---------+-------------+------+-----+---------+-------+
| Field   | Type        | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id      | int         | YES  |     | NULL    |       |
| name    | varchar(20) | YES  |     | NULL    |       |
| age     | int         | YES  |     | NULL    |       |
| birth   | date        | YES  |     | NULL    |       |
| chinese | int         | YES  |     | NULL    |       |
| math    | int         | YES  |     | NULL    |       |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

情况二:复制表结构和数据

sql 复制代码
CREATE TABLE new_tbname SELECT * FROM old_tbname;

如:

mysql 复制代码
mysql> CREATE TABLE student3 SELECT * FROM student;
Query OK, 6 rows affected (0.01 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM student3;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    4 | dijia    |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia   |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
6 rows in set (0.00 sec)

删除数据

当需要删除数据时,是以行为单位的。可以使用DELETE语句或者TRUNCATE语句,其语法规则:

sql 复制代码
# 根据条件删除数据 
DELETE FROM tb_name [ WHERE ];
# 删除表中的所有数据
TRUNCATE tb_name;

接下来,我们在student3表中删除数据

测试1: 删除名字为"taijia"的记录

mysql 复制代码
mysql> DELETE FROM student3 WHERE name='taijia';
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM student3;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    4 | dijia    |   20 | 2000-01-01 |      80 | NULL |
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
5 rows in set (0.00 sec)

测试2:删除数学成绩为NULL的记录

mysql 复制代码
mysql> DELETE FROM student3 WHERE math is NULL;
Query OK, 2 rows affected (0.01 sec)

mysql> SELECT * FROM student3;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
3 rows in set (0.00 sec)

测试3:删除student3表中的所有数据

mysql 复制代码
mysql> TRUNCATE student3;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM student3;
Empty set (0.00 sec)

数据查询语言DQL

数据查询语言(Data Query Language, DQL)是数据库管理系统(DBMS)中用于从数据库中检索或查询数据的语言部分。在关系型数据库管理系统(RDBMS)中,最广泛使用的数据查询语言是SQL(Structured Query Language)的查询部分。

简单查询

简单查询针对于单表而言。DQL的基本结构是以SELECT语句开始,其语法规则如下:

sql 复制代码
SELECT field1, fiedl2, ...
FROM tb_name
[WHERE condition]
[ORDER BY]
[LIMIT ];

SELECT子句

在SQL中,SELECT子句是SELECT语句的核心部分,用于指定从数据库表中检索哪些列的数据。

如果要查看表中的所有数据,则不需要指定属性,直接用通配符* 就可以,它代表要查看所有列。

mysql 复制代码
mysql> SELECT * FROM student;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    4 | dijia    |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia   |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
|    3 | aguru    |   26 | 2005-06-06 |      77 |   66 |
+------+----------+------+------------+---------+------+
6 rows in set (0.00 sec)

如我们只想要查看student表中的name和chinese两个属性,则可以使用如下语句

mysql 复制代码
mysql> SELECT name,chinese FROM student;
+----------+---------+
| name     | chinese |
+----------+---------+
| dijia    |      80 |
| taijia   |      90 |
| leiou    |      70 |
| oubu     |      50 |
| beisuosi |      90 |
| aguru    |      77 |
+----------+---------+
6 rows in set (0.00 sec)
DISTINCT 去除重复值

当查询时,如果结果集中有重复值时,我们需要去重,就可以在SELECT关键字之后加上DISTINCT,且只能在SELECT之后,不能放在属性中间。

mysql 复制代码
mysql> SELECT DISTINCT chinese FROM student;
+---------+
| chinese |
+---------+
|      80 |
|      90 |
|      70 |
|      50 |
|      77 |
+---------+
5 rows in set (0.00 sec)

当distinct作用于单个字段时,去除重复的列即可;

当distinct作用于多个字段时,表示的是多个字段都相同时,才会去除;

distinct关键字必须要放在第一个字段前面,否则会报错。

使用表达式

我们还可以使用表达式对列中的数据进行计算。

mysql 复制代码
mysql> SELECT name, chinese + math FROM student;
+----------+----------------+
| name     | chinese + math |
+----------+----------------+
| dijia    |           NULL |
| taijia   |           NULL |
| leiou    |           NULL |
| oubu     |            105 |
| beisuosi |            185 |
| aguru    |            143 |
+----------+----------------+
6 rows in set (0.00 sec)

也可以用 AS 对表达式取一个别名

mysql 复制代码
mysql> SELECT name, chinese + math as total FROM student;
+----------+-------+
| name     | total |
+----------+-------+
| dijia    |  NULL |
| taijia   |  NULL |
| leiou    |  NULL |
| oubu     |   105 |
| beisuosi |   185 |
| aguru    |   143 |
+----------+-------+
6 rows in set (0.00 sec)

**注意:**select语句得到的结果中出现的列不一定存在数据表中。

数据的存储和数据的展示是两回事

ORDER By子句

选择一个字段进行排序

mysql 复制代码
mysql> SELECT * FROM student ORDER BY chinese;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
|      4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
+--------+----------+-----+------------+---------+--------+
6 rows in set
Time: 0.008s

默认情况下是升序ASC

如下两条语句是等价的

sql 复制代码
SELECT * FROM student ORDER BY chinese;
SELECT * FROM student ORDER BY chinese ASC;

降序排列用DESC

mysql 复制代码
mysql> SELECT * FROM student ORDER BY chinese DESC;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|      4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
+--------+----------+-----+------------+---------+--------+

6 rows in set
Time: 0.007s

如果选择多个字段,第一个是主排序的字段,后续的依次副排序的字段

mysql 复制代码
mysql> SELECT * FROM student ORDER BY age ASC, chinese DESC;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
|      4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
+--------+----------+-----+------------+---------+--------+

6 rows in set
Time: 0.011s

WHERE子句

在SQL中, Where子句是很重要的组成部分,它用来过滤查询结果。你可以指定一个或多个条件,来进行查询结果的过滤。

比较运算符

Where子句中支持多种比较运算符,如

= 等号

<> 或 != 不等于

> 大于

< 小于

>= 大于或等于

<= 小于或等于

如:

sql 复制代码
mysql> select * from student where math > 80;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
+------+----------+------+------------+---------+------+
1 row in set (0.00 sec)
范围查找

between ... and ... 常常一起使用来指定一个值的范围,这个范围包括两个边界的值。

如:

sql 复制代码
mysql> select * from student where chinese between 60 and 80;
+------+-------+------+------------+---------+------+
| id   | name  | age  | birth      | chinese | math |
+------+-------+------+------------+---------+------+
|    4 | dijia |   20 | 2000-01-01 |      80 | NULL |
| NULL | leiou |   22 | 2002-03-03 |      70 | NULL |
|    3 | aguru |   26 | 2005-06-06 |      77 |   66 |
+------+-------+------+------------+---------+------+
3 rows in set (0.00 sec)
逻辑运算符

Where子句中,可以使用多种逻辑运算符来组合或修改条件,包括

AND: 所有条件都必须为真

OR: 至少有一个条件为真

NOT: 条件为假

如:

sql 复制代码
mysql> select * from student where chinese > 80 and age > 20;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
| NULL | taijia   |   21 | 2001-02-02 |      90 | NULL |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
+------+----------+------+------------+---------+------+
2 rows in set (0.00 sec)
mysql 复制代码
mysql> select * from student where chinese > 80 or age > 20;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
+--------+----------+-----+------------+---------+--------+

4 rows in set
Time: 0.008s
mysql 复制代码
mysql> select * from student where not age > 20;
+--------+-------+-----+------------+---------+--------+
|     id | name  | age | birth      | chinese |   math |
+--------+-------+-----+------------+---------+--------+
|      4 | dijia |  20 | 2000-01-01 |      80 | <null> |
| <null> | leiou |  20 | 2002-03-03 |      70 | <null> |
+--------+-------+-----+------------+---------+--------+

2 rows in set
Time: 0.007s
模糊查询

我们还可以使用LIKE进行模糊查询,包括

% 表示任意数量的字符

_ 表示一个字符

如:

sql 复制代码
mysql> select * from student where name LIKE '%b%';
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
+------+----------+------+------------+---------+------+
2 rows in set (0.00 sec)

如:

mysql 复制代码
mysql> select * from student where name like '_e%';
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
+--------+----------+-----+------------+---------+--------+

2 rows in set
IN指定多个值

如果想要查询某个字段是否包含在一组值中,可以直接使用IN运算符。

如:

sql 复制代码
mysql> select * from student where id IN (1, 3, 10);
+------+-------+------+------------+---------+------+
| id   | name  | age  | birth      | chinese | math |
+------+-------+------+------------+---------+------+
|    1 | oubu  |   22 | 2003-04-04 |      50 |   55 |
|    3 | aguru |   26 | 2005-06-06 |      77 |   66 |
+------+-------+------+------------+---------+------+
2 rows in set (0.00 sec)
判空操作

在SQL中,判空操作通常用于检查某个字段是否为NULLNULL在SQL中表示字段没有值,这与字段被设置为0,空字符串('')或其他默认值是不同的。

如:

sql 复制代码
mysql> select * from student where math is NULL;
+------+--------+------+------------+---------+------+
| id   | name   | age  | birth      | chinese | math |
+------+--------+------+------------+---------+------+
|    4 | dijia  |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou  |   22 | 2002-03-03 |      70 | NULL |
+------+--------+------+------------+---------+------+
3 rows in set (0.00 sec)

分页查询

在SQL中,分页查询通常用于从大量数据中检索一部分数据,以便在前端页面进行分页显示。在MySQL中,分页查询采用LIMIT子句来实现,它有以下几种情况。

LIMIT m 表示获取前m条记录

LIMIT m,n 表示偏移m条记录后,获取n条记录

LIMIT m OFFSET n 表示偏移n条记录后,获取m条记录

如:

sql 复制代码
mysql> select * from student;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
|      4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
+--------+----------+-----+------------+---------+--------+


mysql> select * from student limit 3;
+------+--------+------+------------+---------+------+
| id   | name   | age  | birth      | chinese | math |
+------+--------+------+------------+---------+------+
|    4 | dijia  |   20 | 2000-01-01 |      80 | NULL |
| NULL | taijia |   21 | 2001-02-02 |      90 | NULL |
| NULL | leiou  |   22 | 2002-03-03 |      70 | NULL |
+------+--------+------+------------+---------+------+
3 rows in set (0.00 sec)

mysql> select * from student limit 2,3;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
+------+----------+------+------------+---------+------+
3 rows in set (0.00 sec)

mysql> select * from student limit 3 offset 2;
+------+----------+------+------------+---------+------+
| id   | name     | age  | birth      | chinese | math |
+------+----------+------+------------+---------+------+
| NULL | leiou    |   22 | 2002-03-03 |      70 | NULL |
|    1 | oubu     |   22 | 2003-04-04 |      50 |   55 |
|    2 | beisuosi |   24 | 2004-05-05 |      90 |   95 |
+------+----------+------+------------+---------+------+
3 rows in set (0.00 sec)

数据完整性

数据完整性(Data Integrity)是数据库管理中的一个核心概念,它指的是数据的准确性和一致性。在数据库系统中,数据完整性确保数据在传输、存储和处理过程中保持其原始性和正确性,没有未经授权的修改或破坏。

数据完整性包括三个主要方面:实体完整性、域完整性和参照完整性。

实体完整性

实体完整性确保表中的每一行都是唯一的,并且具有唯一的标识符(通常是一个主键)。这意味着在表中不能有重复的行,并且主键字段不能包含NULL值。

主键

在SQL中,主键(Primary Key)用于唯一标识数据库表中的每一行记录。它可以是一个或多个字段的组合,

其具备以下特点:

  1. 唯一: 主键的值必须是唯一的,不允许有重复的值。
  2. 非空:主键字段不能包含NULL
  3. 主键可以加快查询速度:。

主键的作用

(1)物理层面:数据的"物理组织者"

这也是主键和唯一约束最本质的区别。在 MySQL 的 InnoDB 引擎中,主键决定了数据在磁盘上的物理存储顺序

  • 有主键 :表就是一棵巨大的 B+ 树(聚簇索引) 。主键是这棵树的"目录"。数据行(一整行记录)直接挂在 B+ 树最底层的叶子节点上,严格按照主键值的顺序排列
  • 无主键 :MySQL 会自动创建一个 6 字节隐藏列 ROW_ID 来充当"隐式主键"去组织数据。数据按插入顺序堆积在磁盘上。

没有显式主键,表在磁盘上就是一个"无头苍蝇"式的堆表,维护成本极高。

如果创建表时设置了主键,那么主键在创建时会自动建立索引,这也有助于提高查询速度

(2)逻辑层面:行级数据的"身份证号"

主键作为行的唯一标识,具备三个刚性特征:

  • 唯一性(Unique):绝对不允许重复。这是和唯一约束重合的功能。
  • 非空性(Not Null) :绝对不允许为 NULL。这是主键和唯一约束最直接的语法区别(唯一约束允许 NULL)。
  • 稳定性 :一旦设定,严禁修改。因为修改主键值相当于把一个人的身份证号改了,会导致数据库在内核层面进行大量的数据页分裂和移动,性能极差且极易产生数据碎片。

(3)关系层面:维系整个数据库的"骨架"

主键作为外键引用的唯一目标:子表的外键理应引用的是父表的主键。只有主键能代表实体的唯一身份。

注意: 以后只要创建表的时候,都要设置主键,不能没有主键。主键能够保持记录的唯一,不能有重复的记录。 建议都使用整型数据设置主键。

比如,在之前的表中,把id设为主键是比较合适的,因为每一条记录的id都是不同的。

设置主键

(1)当创建表时,设置主键

sql 复制代码
mysql> CREATE TABLE employee(
id INT PRIMARY KEY,
name VARCHAR(20),
age INT,
balance INT
);

或者采用以下方式

sql 复制代码
mysql> CREATE TABLE employee(
id INT,
name VARCHAR(20),
age INT,
balance INT,
PRIMARY KEY(id)
);

在上面的表中,id字段被设置为主键了。

(2)当表已经存在时,想要设置主键则需要修改字段。

sql 复制代码
# 当id不存在时
mysql> ALTER TABLE student ADD id INT PRIMARY KEY;

# 当id存在,但没有设置为主键时
mysql> ALTER TABLE student MODIFY id INT PRIMARY KEY;
# 或者
mysql> ALTER TABLE student ADD PRIMARY KEY(id);

主键自增

设置了主键后,在插入数据时,必须要指定不重复的主键,而有时是无法直接知道主键要设置为多少的。

因此,一般情况下,可以将主键设置为自动增长的,在MySQL中需要使用关键字AUTO_INCREMENT。

如:

sql 复制代码
#当前
mysql> select * from student;
+--------+----------+-----+------------+---------+--------+
|     id | name     | age | birth      | chinese |   math |
+--------+----------+-----+------------+---------+--------+
|      4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
| <null> | taijia   |  21 | 2001-02-02 |      90 | <null> |
| <null> | leiou    |  20 | 2002-03-03 |      70 | <null> |
|      1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|      2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|      3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
+--------+----------+-----+------------+---------+--------+

#先将id中的null补全
UPDATE student SET id = 5 WHERE id IS NULL LIMIT 1;
UPDATE student SET id = 6 WHERE id IS NULL LIMIT 1;

+----+----------+-----+------------+---------+--------+
| id | name     | age | birth      | chinese |   math |
+----+----------+-----+------------+---------+--------+
|  4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
|  5 | taijia   |  21 | 2001-02-02 |      90 | <null> |
|  6 | leiou    |  20 | 2002-03-03 |      70 | <null> |
|  1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|  2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|  3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
+----+----------+-----+------------+---------+--------+

#把id设为非空主键
ALTER TABLE student MODIFY id INT NOT NULL PRIMARY KEY;
#给id字段添加自增属性
ALTER TABLE student MODIFY id INT AUTO_INCREMENT;

#再添加一行,不指定id的记录
mysql> insert into student(name,age,birth,chinese,math) values(
                 -> 'gaiya',27,'1999-7-3',88,89);

#新记录的id为7(在已有记录中最大的id的基础上递增)
+----+----------+-----+------------+---------+--------+
| id | name     | age | birth      | chinese |   math |
+----+----------+-----+------------+---------+--------+
|  1 | oubu     |  22 | 2003-04-04 |      50 |     55 |
|  2 | beisuosi |  24 | 2004-05-05 |      90 |     95 |
|  3 | aguru    |  26 | 2005-06-06 |      77 |     66 |
|  4 | dijia    |  20 | 2000-01-01 |      80 | <null> |
|  5 | taijia   |  21 | 2001-02-02 |      90 | <null> |
|  6 | leiou    |  20 | 2002-03-03 |      70 | <null> |
|  7 | gaiya    |  27 | 1999-07-03 |      88 |     89 |
+----+----------+-----+------------+---------+--------+

7 rows in set

**注意:**设置了主键之后(无论有没有设置自增约束),表中存储的数据会改变存储的顺序,原本是根据插入顺序排序,现在是根据主键id的升序排序。

底层原理是表的重建:原本没设置主键,mysql会产生"隐形主键",也就是数据插入顺序。显式设置了主键之后,底层创建了一张新表,旧表的数据迁移到新表,按照主键升序排列。最后用新表取代旧表。

sql 复制代码
mysql> insert into student(id,name,age,birth,chinese,math) valu
                 -> es(7,'daina',27,'1999-7-3',88,89);
#id=7的记录已经存在,主键必须唯一                       
(1062, "Duplicate entry '7' for key 'student.PRIMARY'")

#添加成功(或者这里直接利用id的自增约束,不需要指定id,自然会是8)
mysql> insert into student(id,name,age,birth,chinese,math) valu
                 -> es(8,'daina',27,'1999-7-3',88,89);
Query OK, 1 row affected

删除主键

对于自动增长的主键,删除操作需要两步:

(1)先删除自动增长的约束

(2)再删除主键

sql 复制代码
#取消了自动增长的约束
mysql> ALTER TABLE student modify id int;
#取消了id的主键地位,并不是真正意义上删除了id这个字段
mysql> ALTER TABLE student drop primary key;

取消了自己设定的主键后,mysql又会启用隐形主键,也就是数据存储顺序回到了最初的插入顺序。

联合主键

在MySQL中,我们还可以设置联合主键(也称为复合主键或复合键),这意味着主键由两个或多个列组成。当表中的任何两个或更多列的组合唯一标识表中的每一行时,可以使用联合主键。

如学生表student中,对学生进行分组,id相同的为同一组学生,则可以将id和name设置为联合主键。

sql 复制代码
mysql> CREATE TABLE student(
id INT ,
name VARCHAR(20),
age INT,
birth DATE,
chinese INT,
math INT,
PRIMARY KEY(id, name)
);

当id和name都相同时,就无法插入数据。

如果在创建表的时候没有设置联合主键,想要追加设置。

首先要删除现有主键(如果有的话),因为主键只能有一个;

然后设置联合主键

sql 复制代码
-- 删除原来的id主键
ALTER TABLE student DROP PRIMARY KEY;
-- 给name和birth设置联合主键
ALTER TABLE student ADD PRIMARY KEY(name, birth);

# 添加的数据中name和birth都与已有数据相同,插入失败
mysql> insert into student(id,name,age,birth,chinese,math) valu
                       -> es(9,'dijia',27,'2000-1-1',88,89);
(1062, "Duplicate entry 'dijia-2000-01-01' for key 'student.PRIMARY'")

# 添加的新数据与旧数据name相同,birth不同,添加成功
mysql> insert into student(id,name,age,birth,chinese,math) valu
                       -> es(9,'dijia',27,'2000-1-2',88,89);
Query OK, 1 row affected
Time: 0.002s

域完整性

域完整性确保表中每一列必须要符合某种特定的数据类型或约束

常用的约束包括非空约束、默认值约束、唯一约束

非空约束

student表中的name字段设置为非空约束

mysql 复制代码
ALTER TABLE student MODIFY name VARCHAR(20) NOT NULL;

如果想取消非空约束

mysql 复制代码
ALTER TABLE student MODIFY name VARCHAR(20) DEFAULT NULL;

默认值约束

student表中的chinese字段设置默认值为60

sql 复制代码
mysql> ALTER TABLE student MODIFY chinese INT DEFAULT 60;

唯一约束

employee表中的name字段设置为唯一约束

sql 复制代码
# 已存在表时
mysql> ALTER TABLE student MODIFY name VARCHAR(20) UNIQUE;
#如果此时表中已经有name相同的多条记录,这一步会报错

# 不存在表时
mysql> CREATE TABLE employee (
id INT,
name VARCHAR(20) UNIQUE,
age INT,
PRIMARY KEY(id),
);

唯一约束允许有NULL,而且多个NULL不算重复。

删除唯一约束

删除employee表中name字段的唯一约束,需要两步:先获取唯一约束的名字,再删除。

注意:删除唯一约束时,必须要用关键字INDEX

sql 复制代码
mysql> show create table student;
+---------+--------------------------------------------------------------------+
| Table   | Create Table                                                       |
+---------+--------------------------------------------------------------------+
| student | CREATE TABLE `student` (                                           |
|         |   `id` int NOT NULL,                                               |
|         |   `name` varchar(20) DEFAULT NULL,                                 |
|         |   `age` int DEFAULT NULL,                                          |
|         |   `birth` date NOT NULL,                                           |
|         |   `chinese` int DEFAULT NULL,                                      |
|         |   `math` int DEFAULT NULL,                                         |
|         |   UNIQUE KEY `name` (`name`)                                       
|
|         | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+---------+--------------------------------------------------------------------+

mysql> ALTER TABLE employee DROP INDEX `name`;

联合唯一约束

此外,唯一约束也可以设置为多列,即联合唯一约束。

(1)创建表时直接定义联合唯一约束

sql 复制代码
mysql> CREATE TABLE employee (
id INT,
name VARCHAR(20),
age INT,
CONSTRAINT uk_employee_id_name UNIQUE (id, name)
);

这里的uk_employee_id_name是自己命名的元数据(一般的命名规则:uk即unique key唯一约束)

如下,可查看

mysql 复制代码
SHOW CREATE TABLE employee;

#输出内容如下
CREATE TABLE `employee` (
`id` int DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`age` int DEFAULT NULL,
UNIQUE KEY `uk_employee_id_name` (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

(2)表已存在后添加联合唯一约束

mysql 复制代码
ALTER TABLE employee ADD CONSTRAINT uk_employee_id_name UNIQUE (id, name);

效果

mysql 复制代码
#第一次插入成功
INSERT INTO employee (id, name, age) VALUES (1, '张三', 25);

#第二次插入同样的 id + name 报错
INSERT INTO employee (id, name, age) VALUES (1, '张三', 30);
-- 报错:Duplicate entry '1-张三' for key 'uk_employee_id_name'

-- 允许 id 相同但 name 不同
INSERT INTO employee (id, name, age) VALUES (1, '李四', 26);  

-- 允许 name 相同但 id 不同
INSERT INTO employee (id, name, age) VALUES (2, '张三', 27); 

如果要删除联合唯一约束,也是要先确认约束名,再执行删除

mysql 复制代码
SHOW CREATE TABLE employee;
ALTER TABLE employee DROP INDEX uk_employee_id_name;

参照完整性

参照完整性 确保两个表之间的数据关系是有效的,它主要通过外键约束来实现。

外键

外键是一个或多个字段的组合,其值引用另一个表的主键.

其作用是为了确保表与表之间的数据一致性,防止出现"孤儿数据"。

假设有 学生表 (子表) 和 班级表(父表)。

  • 学生表中的 class_id(外键) 指向班级表的 id(主键)。
  • 如果没有外键 ,你可能在学生表中插入一个 class_id = 999,但班级表里根本没有 999 这个班。这条学生数据就成了"无主孤魂"------这就是数据不一致
  • 有了外键 ,MySQL 会强制检查:插入或更新 class_id 时,这个值必须在班级表的 id 列中存在,否则操作被拒绝。

定义外键

在MySQL中,使用Foreign Key关键字来定义外键关系。

外键语法规则如下:

Foreign Key (filed) References other_tbname(other_filed)

如存在一张学生表student,现有一张订单表torder,其中的id要参照student表中的主键id

(学生表是父表,订单表是子表,订单表中的s_id必须对应某一个学生id)

sql 复制代码
CREATE TABLE student(
id INT AUTO_INCREMENT,
name VARCHAR(20),
birth Date,
chinese INT,
math INT,
PRIMARY KEY(id)
);

CREATE TABLE torder(
 id INT AUTO_INCREMENT,
 p_name VARCHAR(20),
 s_id INT,
PRIMARY KEY(id)
);

# 表已经存在的情况下,添加外键约束
# constraint代表约束, fk_1是自己给外键约束起的名字(foreign key 1)
mysql> ALTER TABLE torder ADD CONSTRAINT fk_1 
FOREIGN KEY(s_id) REFERENCES student(id);
#torder表的外键s_id引用student表的主键id


# torder不存在的情况下,在创建表时设置外键约束
mysql> CREATE TABLE torder(
id INT AUTO_INCREMENT,
p_name VARCHAR(20),
s_id INT,
PRIMARY KEY(id),
 CONSTRAINT fk_torder_student FOREIGN KEY(s_id) REFERENCES student(id)
);

外键名(字段名/列名)是表中实实在在的一列数据,在本例中即s_id,是存储的具体的数据值。

外键约束名(规则名)是附加在表上的一个规则/标签,在本例中即fk_torder_student,告诉 MySQL 执行检查规则,本身不存数据。

可以这样理解:

外键字段(s_id) 就像是家里的大门。

外键约束名(fk_torder_student) 就像是贴在大门上的一张"安检标签"。标签上写着:"所有进入大门的人,必须持有学生证的才能进。"这个标签本身不是门,但它决定了门怎么用。

效果

sql 复制代码
#创建学生表后,加入一条记录
insert into student (id,name,birth,chinese,math) values(1,'Jack','1996.11.23',99,88);

# 成功:因为student 表里有 id=1 的学生
INSERT INTO torder (p_name, s_id) VALUES ('《数据库入门》', 1);

# 失败:student 表里没有 id=99 的学生
INSERT INTO torder (p_name, s_id) VALUES ('《深入浅出MySQL》', 99);
-- 报错:Cannot add or update a child row: a foreign key constraint fails

删除外键

先获取外键约束的名字

再删除外键约束

如:

sql 复制代码
#获取外键约束的名字
show create table torder;
+--------+-------------------------------------------------------------------------------------+
| Table  | Create Table                                                                        |
+--------+-------------------------------------------------------------------------------------+
| torder | CREATE TABLE `torder` (                                                             |
|        |   `id` int NOT NULL AUTO_INCREMENT,                                                 |
|        |   `p_name` varchar(20) DEFAULT NULL,                                                |
|        |   `s_id` int DEFAULT NULL,                                                          |
|        |   PRIMARY KEY (`id`),                                                               |
|        |   KEY `fk_1` (`s_id`),                                                              |
|        |   CONSTRAINT `fk_1` FOREIGN KEY (`s_id`) REFERENCES `student` (`id`)                |
|        | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+--------+-------------------------------------------------------------------------------------+

#删除外键约束
ALTER TABLE torder DROP FOREIGN KEY fk_1;
Query OK, 0 rows affected (0.00 sec)

DQL 之复杂查询

连接查询

数据库中共存储了 A 表和 B 表,经过交叉连接之后,就得到了一张大表。该表是一张临时表,占据内存,并没有真正存储在数据库中(数据库中实际的数据存在磁盘上)。

如果查询得到的这个结果集很大,内存可能放不下,MySQL 就会把它写到磁盘上的临时目录里,用完了再删掉。

连接查询涉及到多张表,主要包括以下几种类型:

  • 交叉连接(笛卡尔积)
  • 内连接
  • 外连接
    • 左外连接
    • 右外连接

交叉连接(笛卡尔积)

语法格式:

sql 复制代码
-- 显式写法
SELECT * FROM LeftTable CROSS JOIN RightTable;

-- 简写(省略 CROSS)
SELECT * FROM LeftTable JOIN RightTable;

-- 隐式写法(不推荐,但常见)
SELECT * FROM LeftTable, RightTable;

说明:

  • 交叉连接返回的是两个表的笛卡尔积,即左表的每一行与右表的每一行组合。
  • 如果 A 表有 m 行,B 表有 n 行,结果集为 m × n 行。

示例:

  • A 表数据:1, 2

  • B 表数据:3, 4, 5

  • 交叉连接结果:

    1-3, 1-4, 1-5
    2-3, 2-4, 2-5

笛卡尔积的筛选

在实际业务中,我们通常不会直接使用完整的笛卡尔积,而是通过 WHEREON 子句对其进行筛选,得到有意义的关联数据。

内连接

语法格式:

sql 复制代码
SELECT * FROM A INNER JOIN B ON A.field1 = B.field2;

作用:

  • 返回两个表中满足连接条件的记录(即交集)。
  • 内连接只返回匹配的行,不匹配的行会被丢弃。

student表

torder表

内连接查询

sql 复制代码
SELECT * FROM student INNER JOIN torder ON student.id = torder.s_id;

得到有订单的学生信息及其订单信息

隐式写法(不推荐):

sql 复制代码
SELECT * FROM A, B WHERE A.field1 = B.field2;

连接查询取别名

在进行连接查询时,可以给每一张表取一个别名,方便使用。

sql 复制代码
#只查询部分字段(满足student的主键与torder的外键对应的)
#表名太长,取别名更方便
#字段名也可以取别名
SELECT s.id, s.name, o.id AS order_id, o.p_name
FROM student AS s
INNER JOIN torder AS o ON s.id = o.s_id;

外连接

外连接又分为左外连接和右外连接

左外连接(LEFT JOIN)

语法格式:

sql 复制代码
SELECT * FROM A LEFT JOIN B ON A.field1 = B.field2;

特点:

  • 左表为主表,左表中的所有数据都会出现。

  • 当右表中没有与之对应的记录时,查询出的右表中的数据全部用 NULL 代替。

  • 左连接结果 = 左表全部 + 右表匹配部分

sql 复制代码
SELECT * FROM student AS s
LEFT JOIN torder AS o ON s.id = o.s_id;

------ 如果只想查看部分字段

**

sql 复制代码
SELECT s.id, s.name, o.id AS order_id, o.p_name
FROM student AS s
LEFT JOIN torder AS o ON s.id = o.s_id;

------查看左连接独有记录

sql 复制代码
#注意比对字段是否为空用的是is null,而不是 = null
select * from student left join torder on student.id = torder.s_id where torder.id is null;
右外连接(RIGHT JOIN)

语法格式:

sql 复制代码
SELECT * FROM A RIGHT JOIN B ON A.field = B.field;

特点:

  • 右表为主表,右表中的所有数据都会出现。

  • 当左表中没有与之对应的记录时,查询出的左表中的数据全部用 NULL 代替。

  • 右连接结果 = 右表全部 + 左表匹配部分

sql 复制代码
SELECT * FROM student AS s
RIGHT JOIN torder AS o ON s.id = o.s_id;

想要展示坐标补充NULL的效果,我们可以给torder表添加一个在学生表里找不到对应学生的订单。但是现在torder表有外键约束,无法添加这样的数据,先删除掉外键约束,再添加。

sql 复制代码
#获取外键约束名
mysql>show create table torder;

| torder | CREATE TABLE `torder` (                                                             |
|        |   `id` int NOT NULL AUTO_INCREMENT,                                                 |
|        |   `p_name` varchar(20) DEFAULT NULL,                                                |
|        |   `s_id` int DEFAULT NULL,                                                          |
|        |   PRIMARY KEY (`id`),                                                               |
|        |   KEY `fk_1` (`s_id`),                                                              |
|        |   CONSTRAINT `fk_1` FOREIGN KEY (`s_id`) REFERENCES `student` (`id`)                |

#删除外键约束
mysql> alter table torder drop foreign key fk_1;       
                                  
#添加一个不对应任何学生的订单
INSERT INTO torder (p_name, s_id) VALUES ('《网络协议》', 99);    

#查看torder表
select * from torder;
+----+----------------+------+
| id | p_name         | s_id |
+----+----------------+------+
|  1 | 《数据库入门》 |    1 |
|  2 | 《数据结构》   |    1 |
|  3 | 《算法》       |    3 |
|  4 | 《网络协议》   |   99 |
+----+----------------+------+

#右连接查询
SELECT * FROM student AS s
RIGHT JOIN torder AS o ON s.id = o.s_id;

------ 查看右连接所独有的记录

sql 复制代码
select * from student right join torder on student.id = torder.s_id where student.id is null;

连接查询总结

------ 外连接与内连接的区别如下:

对比 内连接(INNER JOIN) 外连接(LEFT / RIGHT JOIN)
返回结果 只返回两表匹配的行 返回主表全部 + 另一表匹配的行(不匹配则补 NULL
主表 无主次之分 有主次之分(LEFT 左表为主,RIGHT 右表为主)
丢弃数据 不匹配的行直接丢弃 主表不匹配的行保留 (另一侧补 NULL

------ 左连接与右连接的效果:

连接类型 SQL 语句 说明
左连接 SELECT * FROM A LEFT JOIN B ON A.id = B.id; 左表全部,右表匹配或 NULL
左表独有 SELECT * FROM A LEFT JOIN B ON A.id = B.id WHERE B.id IS NULL; 只在左表,不在右表
右连接 SELECT * FROM A RIGHT JOIN B ON A.id = B.id; 右表全部,左表匹配或 NULL
右表独有 SELECT * FROM A RIGHT JOIN B ON A.id = B.id WHERE A.id IS NULL; 只在右表,不在左表

图示:

------全连接(取并集)

子查询

子查询也叫嵌套查询 ,是指在 WHERE 子句或 FROM 子句中又嵌入 SELECT 查询语句。

where子查询也称作where子句的嵌套查询;

from子查询也称作from子句的嵌套查询。

WHERE 子查询

场景:查询语文成绩高于95 分的学生的订单信息。

student

torder

嵌套查询

sql 复制代码
SELECT * FROM torder
WHERE s_id IN (SELECT id FROM student WHERE chinese > 95);

只有Jack的语文成绩高于95,查询到Jack(id=1)对应的所有订单。

FROM 子查询

场景:查询学号大于1 的学生所购买的产品,显示学生 id、姓名以及购买的产品。

sql 复制代码
SELECT s.id, s.name, o.p_name
FROM (SELECT * FROM student WHERE id > 1) AS s
JOIN torder AS o 
ON s.id = o.s_id;

这里注意区分一下where子查询和from子查询。

  • WHERE 子查询 :把结果当作一个"值"或"值的列表",用来做条件过滤。
  • FROM 子查询 :把结果当作一张"临时的表",用来继续做查询。
对比 WHERE 子查询 FROM 子查询
结果的性质 被当作一个 (标量)或一列值(列表) 被当作一张临时的表(有行有列)
结果的行数 通常返回 1 列 (多行)或 1 个值 可以返回多行多列
在 SQL 中的角色 充当筛选条件 充当数据源

联合查询

联合查询能够合并两条查询语句的查询结果,去掉其中的重复数据行,然后返回没有重复数据行的查询结果。

语法格式:

sql 复制代码
SELECT ... UNION SELECT ...;

说明:

  • UNION 默认会去重。
  • 如果需要保留所有重复行,使用 UNION ALL(性能更好)。

当前两张表

需求一:把学生的名字和商品的名称放在同一列里展示。

sql 复制代码
SELECT name FROM student
UNION
SELECT p_name FROM torder;

需求二 :把学生的 id 和订单的 s_id 合并成一列

学生id有1,2,3;

订单s_id有1,1,3,99

sql 复制代码
SELECT id FROM student
UNION 
SELECT s_id FROM torder;

------ 使用UNION会去重

sql 复制代码
SELECT id FROM student
UNION ALL
SELECT s_id FROM torder;

------ 使用UNION ALL不去重。

报表查询

统计函数

常用统计函数:

函数 作用
COUNT(*) 计数,统计行数
SUM(列) 求和
AVG(列) 求平均值
MAX(列) 求最大值
MIN(列) 求最小值

示例:

sql 复制代码
SELECT COUNT(*) FROM student;          -- 学生总人数
SELECT SUM(chinese) FROM student;      -- 语文总成绩
SELECT AVG(chinese) FROM student;      -- 语文平均分
SELECT MAX(chinese) FROM student;      -- 语文最高分
SELECT MIN(math) FROM student;         -- 数学最低分

示例一:

示例二:

示例三:

注:除了COUNT(*),有时也会见到COUNT(字段名)的用法

  • COUNT(*) :统计所有行的数量 ,不管字段值是否为 NULL
  • COUNT(字段名) :统计该字段中值不为 NULL 的行数。

GROUP BY 分组

GROUP BY 用于将数据按某个字段进行分组,通常配合统计函数使用。

先给student表多增加一些数据

按语文成绩进行分组,查询成绩值与每组的人数。

sql 复制代码
SELECT chinese, COUNT(*) 
FROM student 
GROUP BY chinese;

分组过滤(HAVING)

当分组之后,还需要对记录进行过滤,只能使用 HAVING 子句

sql 复制代码
SELECT chinese, COUNT(*) 
FROM student 
GROUP BY chinese;
HAVING COUNT(*) > 1;

与having有些类似的,where子句也是起过滤效果。

但不同的是,WHERE 子句在分组之前使用,过滤的是原始行;

HAVING 在分组之后使用,过滤的是分组之后的结果组。

group by之后不能再用where

综合示例

需求:按语文成绩进行分组,查看语文成绩在 60 分以上的得分情况,同时统计数学的最高分和最低分,并根据语文成绩降序排列。

sql 复制代码
SELECT
    chinese,
    COUNT(chinese),
    max(math),
    min(math)
FROM student
WHERE chinese > 90
GROUP BY chinese
HAVING chinese > 95
ORDER BY chinese;

下面来看看这么复杂的语句,具体执行的步骤:

第 1 步:FROM student ------ 取出所有原始数据

id name birth chinese math
1 Jack 1996-11-23 99 88
2 Rose 1999-04-02 88 99
3 Alice 2000-01-01 95 92
4 Tom 1998-04-23 99 78
5 Bob 1999-06-05 95 89
6 Lucy 2001-01-01 97 85

第 2 步:WHERE chinese > 90 ------ 筛选行

只看语文成绩 大于 90 的学生。

id name birth chinese math 是否保留
1 Jack 1996-11-23 99 88 保留(99 > 90)
2 Rose 1999-04-02 88 99 剔除(88 ≤ 90)
3 Alice 2000-01-01 95 92 保留(95 > 90)
4 Tom 1998-04-23 99 78 保留(99 > 90)
5 Bob 1999-06-05 95 89 保留(95 > 90)
6 Lucy 2001-01-01 97 85 保留(97 > 90)

筛选后的数据

id name birth chinese math
1 Jack 1996-11-23 99 88
3 Alice 2000-01-01 95 92
4 Tom 1998-04-23 99 78
5 Bob 1999-06-05 95 89
6 Lucy 2001-01-01 97 85

Rose(88分)被 WHERE 提前筛掉了,不参与任何后续分组和统计


第 3 步:GROUP BY chinese ------ 按语文成绩分组

把上一步保留的数据,按 chinese 值分组:

组(chinese) 组内成员 行数
99 Jack(99分)、Tom(99分) 2 行
95 Alice(95分)、Bob(95分) 2 行
97 Lucy(97分) 1行

第 4 步:HAVING chinese > 95 ------ 筛选组

在分组完成后,筛选出 chinese > 95 的组。

组(chinese) 是否满足 chinese > 95
99 满足(99 > 95)
95 不满足(95 > 95 为假)
97 满足(97 > 95)

结果:95 分组被淘汰,剩下 99和97 分组。


第 5 步:SELECT 聚合统计

对 99 分组进行统计:

  • COUNT(chinese):该组人数 = 2(Jack 和 Tom)
  • MAX(math):数学最高分 = max(88, 78) = 88
  • MIN(math):数学最低分 = min(88, 78) = 78

对97分组进行统计:

  • COUNT(chinese):该组人数 = 1(Lucy)
  • MAX(math):数学最高分 = max(85) = 85
  • MIN(math):数学最低分 = min(85) = 85

统计结果

chinese 人数 数学最高分 数学最低分
99 2 88 78
97 1 85 85

第 6 步:ORDER BY chinese ------ 默认升序排列

最终结果

复杂查询小结

在写 SQL 语句时,涉及到多表查询,都可以从先得到一张大表(临时表)后,再开始根据需求进行条件过滤,逐步进行。

复杂查询可以使用图形化界面的工具,更方便。

数据库的备份和恢复

备份

在终端(命令行)下执行:

bash 复制代码
$ mysqldump -u root -p wangdao > wangdao.sql

该命令将 wangdao 数据库导出为 wangdao.sql 脚本文件,保存目录即为当前所在目录。

恢复

  1. 先在 MySQL 服务器上创建一个空的数据库(如 wangdao2):
sql 复制代码
CREATE DATABASE wangdao2;
  1. 两种恢复方式:

方式一:在命令行下

bash 复制代码
$ mysql -u root -p wangdao2 < wangdao.sql

方式二:进入 MySQL 后

sql 复制代码
USE wangdao2;
SOURCE wangdao.sql;

C 语言的 MySQL API

相关结构体

MYSQL代表一个MYSQL连接

  1. 通过 mysql_init() 初始化。
  2. 通过 mysql_real_connect() 建立连接。
  3. 所有后续操作(查询、获取错误信息等)都需要通过这个句柄。
  4. 最后用 mysql_close() 关闭并释放。

MySQL的服务端与客户端之间的通信是通过socket

MYSQL_RES代表结果集

代表 SELECTSHOW等查询返回的完整结果集。

  • 结果集包含了所有行列元数据(字段名、类型等)。
  • 它是在客户端内存中分配的(通过 mysql_store_result())或由服务器流式返回(通过 mysql_use_result())。
  • 使用完后必须释放,否则会造成内存泄漏。

MYSQL_ROW代表结果集中的一行数据

它是一个 char** 类型的指针数组,每个元素指向一个字段值的字符串表示。

  • 每一行通过 mysql_fetch_row() 返回。
  • 行的每一列通过下标访问:row[0]row[1]、...、row[字段数-1]
  • 如果某个字段值为 NULL,对应的指针为 NULL

MYSQL_FIELD代表列(字段)

代表结果集中一列的元数据,描述列的结构信息。

常用函数介绍

mysql_init()

c 复制代码
MYSQL * mysql_init(MYSQL* mysql);

功能:

分配或初始化与 mysql_real_connect() 相适应的 MYSQL 对象。

  • 如果传入的参数mysqlNULL 指针,MYSQL会在在堆内存中动态分配 一个新的 MYSQL 结构体对象,并把它初始化好,然后返回这个对象的地址。
  • 如果传入的参数是一个已经分配好内存的 MYSQL 结构体变量的地址,那么MySQL 不会重新分配内存,而是把传进去的那个对象重新初始化,然后返回该对象的地址。
  • 如果 mysql_init() 分配了新的对象,使用完后,必须调用 mysql_close(conn),它会自动释放这个对象占用的内存。

返回值:

  • 成功:初始化的 MYSQL* 句柄
  • 失败:NULL(内存不足)

mysql_real_connect()

c 复制代码
MYSQL* mysql_real_connect(MYSQL *mysql,
                   const char *host,
                   const char *user,
                   const char *passwd,
                   const char *db,
                   unsigned int port,
                   const char *unix_socket,
                   unsigned long client_flag);

功能:

尝试与运行在 host 上的 MySQL 服务器建立连接。

在执行任何其他需要有效 MYSQL 连接句柄的 API 函数之前,客户端程序必须成功连接到服务器。

参数说明:

参数 类型 说明 常用值
mysql MYSQL* mysql_init() 初始化的连接句柄 conn
host const char* MySQL 服务器主机名或 IP 地址 "localhost""127.0.0.1"
user const char* 登录用户名 "root"
passwd const char* 登录密码 "1234"
db const char* 要连接的数据库名(可传 NULL,连接后再用 mysql_select_db() 选择) "test"NULL
port unsigned int MySQL 服务器端口号,默认 3306,传入 0 表示使用默认端口 0
unix_socket const char* Unix 套接字文件路径(Windows 下忽略),一般传 NULL NULL
client_flag unsigned long 客户端标志位,一般传 0 0

返回值:

  • 成功:返回 MYSQL* 连接句柄(与第 1 个参数值相同)
  • 失败:返回 NULL

错误信息:

如果发生错误,mysql_error(conn) 返回以 null 结尾的字符串;如果没有错误,返回空字符串。


mysql_select_db函数

mysql_select_db() 是用来切换当前数据库连接的默认数据库的函数。

c 复制代码
int mysql_select_db(MYSQL *mysql, const char *db)

参数:

  • 连接句柄
  • 想要切换到的目标数据库的名称

返回值:

函数执行成功时返回 0;如果发生任何错误,则返回一个非零值

mysql_query() / mysql_real_query()

c 复制代码
int mysql_query(MYSQL *mysql, const char *stmt_str);
int mysql_real_query(MYSQL *mysql, const char *stmt_str, unsigned long length);

功能:

执行 SQL 语句。

  • mysql_query():执行以 \0 结尾的字符串指向的 SQL 语句。
  • mysql_real_query():执行SQL 语句,长度为 length 字节。

------ 为什么要设计这样两个不同形式的函数呢?

mysql_query() 是 MySQL 最早期的 API 函数,设计得非常简单:你给它一个字符串,它就执行。这在处理纯文本 SQL(比如 SELECT * FROM student)时完全够用。

但随着 MySQL 的发展,用户开始往数据库里存图片、音频等二进制文件,也就是说参数stmt_str可能本身就是包含了\0的SQL语句。这时候 mysql_query() 的简单设计就成了致命缺陷------因为它无法处理包含 \0 空字符的 SQL。但已经有很多老项目依赖 mysql_query() 了,MySQL 不能直接删掉它,于是新增了 mysql_real_query() 来解决这个问题。

sql 复制代码
CREATE TABLE images (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    data BLOB   -- 二进制大对象,用于存储图片数据
);                                    
c 复制代码
//在准备好的存放SQL语句的缓冲器buff中保存"INSERT INTO images (name, data) VALUES ('photo1', 'FF D8 FF E0 \0 10 4A 46 49 46 \0 01 ...')"
mysql_query(mysql,buff); //这样就会读取出问题
 mysql_real_query(mysql,buff,total_len);//这样就OK

注意事项:

  • 通常,字符串必须由单个 SQL 语句 组成,不带终止分号 ;
  • 如果启用了多语句执行,则可以包含多个以分号分隔的语句(最后一个语句的后面可以加分号,也可以不加)。

(在调用 mysql_real_connect() 时,将最后一个参数 client_flag 设置为 CLIENT_MULTI_STATEMENTS,即开启多语句执行)

返回值:

  • 成功:0
  • 失败:非 0

常见错误码:

错误码 说明
CR_COMMANDS_OUT_OF_SYNC 以不恰当的顺序执行了命令
CR_SERVER_GONE_ERROR MySQL 服务器不可用
CR_SERVER_LOST 在查询过程中与服务器的连接丢失
CR_UNKNOWN_ERROR 出现未知错误

获取查询结果

方式一:mysql_store_result()
c 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);

特点:

  • 将查询的全部结果 读取到客户端(此过程是阻塞式的),分配一个 MYSQL_RES 结构,并将结果置于该结构中。
  • 如果查询未返回结果集,返回 NULL 指针。
  • 一旦调用了 mysql_store_result() 并获得了非 NULL 的结果,可调用:
    • mysql_num_rows():获取结果集中的行数
    • mysql_fetch_row():获取结果集中的一行,放到循环中就可以逐行遍历(此时数据已经在客户端内存中,遍历速度很快)
    • mysql_row_seek() :跳转到某一行
    • mysql_row_tell():获取当前行位置
  • 完成操作后,必须调用 mysql_free_result() 释放内存。
方式二:mysql_use_result()
c 复制代码
MYSQL_RES *mysql_use_result(MYSQL *mysql);

特点:

  • 并不会真正获取数据,只是在MYSQL服务器端开启了一个数据流管道。
  • 只有当调用 mysql_fetch_row() 函数后才真正获取数据,每调用一次获取一行数据。
  • 每次是直接从服务器读取结果。
  • mysql_store_result() 相比,使用的内存更少(因为不需要存整个结果集,每次读只存一行)。

返回值:

  • 成功:返回 MYSQL_RES* 结果
  • 失败:返回 NULL

**注:**当查询语句的结果集很大时,更适合使用方式二。

mysql_fetch_row()

c 复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); 

功能:

检索结果集的下一行。

  • mysql_store_result() 之后使用:如果没有要检索的行,返回 NULL
  • mysql_use_result() 之后使用:如果没有要检索的行或出现了错误(比如网络连接中断),返回 NULL

访问方式:

c 复制代码
//MYSQL_ROW就是char**,得到一行的内容以字符串数组的方式存放
MYSQL_ROW row = mysql_fetch_row(res);
//行内值的数目(一行有多少列)由 mysql_num_fields函数得出
int num_fields = mysql_num_fields(res);
//得到一个long型数组的首元素地址,数组中保存的是得到的一行内容中每一列(每个字段)的数据长度
unsigned long *lengths = mysql_fetch_lengths(res);; 

//按照 `row[0]` 到 `row[mysql_num_fields(result) - 1]` 访问,遍历此行的每一列
for (int i = 0; i < num_fields; i++) {
        // 第一步:判断是否为 NULL
        if (row[i] == NULL) {
            printf("NULL\t");
            continue;
        }
        
        // 第二步:判断是否为空字符串(长度为 0)
        if (lengths[i] == 0) {
            printf("(空字符串)\t");
            continue;
        }
        
        // ★ 第三步:有实际数据
        printf("%s\t", row[i]);
    }

------ mysql_fetch_lengths() 的使用方法

c 复制代码
unsigned long *mysql_fetch_lengths(MYSQL_RES *result);

这个函数返回一个 unsigned long* 数组,每个元素对应一列的长度。必须在调用 mysql_fetch_row() 之后立即调用,因为长度信息与当前行绑定。


mysql_free_result() / mysql_close()

c 复制代码
void mysql_free_result(MYSQL_RES *result);
void mysql_close(MYSQL *mysql);
  • mysql_free_result():释放由 mysql_store_result()mysql_use_result() 等为结果集分配的内存。完成对结果集的操作后必须调用,释放完成后不要尝试访问结果集。
  • mysql_close():关闭前面打开的连接。如果句柄是由 mysql_init()mysql_connect() 自动分配的,mysql_close() 还将解除分配连接句柄。

C 语言 API 示例代码

编译方式:

gcc Test.c -lmysqlclient

写操作示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>  //加载mysql客户端的头文件

int main()
{
    //MYSQL conn;
    //1. 初始化MYSQL结构体
    MYSQL * pconn = mysql_init(NULL);
    if(pconn == NULL) {
        printf("mysql_init failed\n");
        return EXIT_FAILURE;
    }

    //2. 连接数据库
    const char * host = "localhost";
    const char * user = "root";
    const char * passwd = "1234";
    const char * db = "7th";
    //连接成功时, ret的指向与pconn是相同的
    MYSQL * ret = mysql_real_connect(pconn, host, user, passwd, db, 0, NULL, 0);
    if(ret == NULL) {
        printf("mysql connect failed: %s.\n", mysql_error(pconn));
        return EXIT_FAILURE;
    }
    printf("ret: %p, pconn: %p\n", ret, pconn);

    //3. 执行查询
    const char * sql = "UPDATE student set chinese=80 where id=8";
    int nret = mysql_real_query(pconn, sql, strlen(sql));
    if(nret != 0) {
        //查询失败
        printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
        mysql_close(pconn);
        return EXIT_FAILURE;
    }

    //4. 程序如果能执行到这里,就是写操作成功了
    printf("Query OK, %ld row affected.\n", mysql_affected_rows(pconn));
    //5. 关闭mysql的连接
    mysql_close(pconn);

    return 0;
}

读操作示例

c 复制代码
#include <mysql/mysql.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    // 1. 初始化结构体 MYSQL
    MYSQL *conn = mysql_init(NULL);
    const char *host = "localhost";
    const char *user = "root";
    const char *passwd = "1234";
    const char *db = "7th";

    // 2. 与 MySQL 服务器建立连接
    conn = mysql_real_connect(conn, host, user, passwd, db, 0, NULL, 0);
    if (!conn) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        return EXIT_FAILURE;
    }

    // 3. 执行查询操作
    const char *query = "SELECT * FROM student";
    int ret = mysql_query(conn, query);
    if (ret != 0) {
        fprintf(stderr, "%d, %s\n", mysql_errno(conn), mysql_error(conn));
        return EXIT_FAILURE;
    }

    // 4. 获取查询的结果集
    MYSQL_RES *res = mysql_store_result(conn);
    if (res) {
        // 获取字段名的信息
        MYSQL_FIELD *field = NULL;
        while ((field = mysql_fetch_field(res)) != NULL) {
            printf("%s\t", field->name);
        }
        printf("\n");

        int rows = mysql_num_rows(res);
        int cols = mysql_num_fields(res);

        // 获取每一行的数据
        MYSQL_ROW row;
        while ((row = mysql_fetch_row(res)) != NULL) {
            for (int i = 0; i < cols; ++i) {
                printf("%s\t", row[i]);
            }
            printf("\n");
        }
    }

    // 5. 释放结果集(实际生产代码中需要调用)
    mysql_free_result(res);
    // 6. 关闭连接
    mysql_close(conn);

    return 0;
}

经典问题

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>  //加载mysql客户端的头文件

int main()
{
    //MYSQL conn;
    //1. 初始化MYSQL结构体
    MYSQL * pconn = mysql_init(NULL);
    if(pconn == NULL) {
        printf("mysql_init failed\n");
        return EXIT_FAILURE;
    }

    //2. 连接数据库
    const char * host = "localhost";
    const char * user = "root";
    const char * passwd = "1234";
    const char * db = "7th";
    //连接成功时, ret的指向与pconn是相同的
    MYSQL * ret = mysql_real_connect(pconn, host, user, passwd, db, 0, NULL, 0);
    if(ret == NULL) {
        printf("mysql connect failed: %s.\n", mysql_error(pconn));
        return EXIT_FAILURE;
    }
    printf("ret: %p, pconn: %p\n", ret, pconn);

    //3. 执行查询
    const char * sql = "select * from student";
    printf("第一次执行查询\n");
    int nret = mysql_real_query(pconn, sql, strlen(sql));
    if(nret != 0) {
        //查询失败
        printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
        mysql_close(pconn);
        return EXIT_FAILURE;
    }

    /* 1. 每次 SELECT 后,必须调用 mysql_store_result() 或 mysql_use_result() 获取结果集。 */
    /* 2. 调用 mysql_fetch_row() 读取完所有行。 */
    /* 3. 调用 mysql_free_result() 释放结果集。 */
    /* 4. 然后才能执行下一次查询。 */
    printf("\n第二次执行查询\n");
    nret = mysql_real_query(pconn, sql, strlen(sql));
    if(nret != 0) {
        //查询失败
        printf("ERROR %d: %s\n", mysql_errno(pconn), mysql_error(pconn));
        mysql_close(pconn);
        return EXIT_FAILURE;
    }

    //4. 程序如果能执行到这里,就是读操作成功了, 获取结果集
    //printf("Query OK, %ld row affected.\n", mysql_affected_rows(pconn));
    MYSQL_RES * result = mysql_store_result(pconn);
    if(result) {
        //获取到了结果集
        int rows = mysql_num_rows(result);//9行
        int cols = mysql_num_fields(result);//6列
        printf("res: (%d, %d)\n", rows, cols);
        //打印字段名
        MYSQL_FIELD * filed;
        while((filed = mysql_fetch_field(result)) != NULL) {
            printf("%s\t", filed->name);
            //filed->db;//数据库的名字
            //filed->table;//表的名字
        }
        printf("\n");

        //打印每一行
        MYSQL_ROW row;
        while((row = mysql_fetch_row(result)) != NULL) {
            for(int i = 0; i < cols; ++i) {
                printf("%s\t", row[i]);
            }
            printf("\n");
        }
    }
    //5. 释放结果集
    mysql_free_result(result);

    //6. 关闭mysql的连接
    mysql_close(pconn);

    return 0;
}
相关推荐
阿米亚波1 小时前
【C++ STL】std::unordered_multiset
开发语言·数据结构·c++·笔记·stl
随意起个昵称1 小时前
贪心模型-Johnson法则
c++·算法
蜡笔小马1 小时前
【保姆级教程】Windows 下 CGAL 6.2 + VS2022 全流程实战:从源码编译到项目集成(附避坑指南)
c++·cgal
Nontee1 小时前
设计模式:模板方法与策略,从“每个字都认识“到能说清它们在干嘛
java·数据库·设计模式
xiaoye-duck2 小时前
《Linux系统编程》Linux 系统多线程(八): C++ 高并发线程池全链路深度解析与从零手撕实现
linux·c++·线程池
眞bilibili2 小时前
带团队后的日常思考(十七)
数据库
laboratory agent开发2 小时前
智能体多工具串联执行中途失败,部分写入的数据如何回滚
运维·服务器·数据库
胖大和尚2 小时前
在C++的类中,是否可以把函数声明成__device__
c++·cuda
十年磨剑走天涯2 小时前
C++ STL 容器操作复杂度速查表
开发语言·c++
ziguo11223 小时前
Windows API MessageBox 函数详解
c语言·c++·windows·visualstudio