SQL语法

SQL语法

整理了一下数据库系统概论上的SQL的语法

文章目录

  • SQL语法
    • 数据定义
      • [一、 模式 (Schema)](#一、 模式 (Schema))
        • [1. 创建模式 (DBA权限)](#1. 创建模式 (DBA权限))
        • [2. 删除模式](#2. 删除模式)
      • [二、 表 (Table)](#二、 表 (Table))
        • [1. 创建表](#1. 创建表)
        • [2. 修改表](#2. 修改表)
        • [3. 删除表](#3. 删除表)
        • [4. 数据类型与约束条件速查表](#4. 数据类型与约束条件速查表)
      • [三、 视图 (View)](#三、 视图 (View))
        • [1. 创建视图](#1. 创建视图)
        • [2. 删除视图](#2. 删除视图)
      • [四、 索引 (Index)](#四、 索引 (Index))
        • [1. 创建索引](#1. 创建索引)
        • [2. 删除索引](#2. 删除索引)
    • 数据查询
      • [一、SELECT 基本查询结构](#一、SELECT 基本查询结构)
        • [1. 语句格式](#1. 语句格式)
        • [2. 示例](#2. 示例)
      • [二、WHERE 条件查询](#二、WHERE 条件查询)
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • [三、GROUP BY 分组查询](#三、GROUP BY 分组查询)
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • [四、ORDER BY 排序查询](#四、ORDER BY 排序查询)
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • 五、表连接查询 (JOIN)
        • [1. 语句格式](#1. 语句格式)
          • [1.1 等值连接](#1.1 等值连接)
          • [1.2 自连接](#1.2 自连接)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • 六、子查询
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
          • [2.1 IN 操作符](#2.1 IN 操作符)
          • [2.2 EXISTS 操作符](#2.2 EXISTS 操作符)
            • [使用 EXISTS 实现全称量词](#使用 EXISTS 实现全称量词)
            • [使用 EXISTS 实现逻辑蕴含](#使用 EXISTS 实现逻辑蕴含)
            • 核心思路与技巧
          • [2.3 ANY/SOME 操作符](#2.3 ANY/SOME 操作符)
          • [2.4 ALL 操作符](#2.4 ALL 操作符)
        • [3. 示例](#3. 示例)
      • 七、聚集函数
        • [1. COUNT() 函数](#1. COUNT() 函数)
          • [1.1 语句格式](#1.1 语句格式)
          • [1.2 使用说明和注意事项](#1.2 使用说明和注意事项)
          • [1.3 示例](#1.3 示例)
        • [2. SUM() 函数](#2. SUM() 函数)
          • [2.1 语句格式](#2.1 语句格式)
          • [2.2 使用说明和注意事项](#2.2 使用说明和注意事项)
          • [2.3 示例](#2.3 示例)
        • [3. AVG() 函数](#3. AVG() 函数)
          • [3.1 语句格式](#3.1 语句格式)
          • [3.2 使用说明和注意事项](#3.2 使用说明和注意事项)
          • [3.3 示例](#3.3 示例)
        • [4. MAX() 函数](#4. MAX() 函数)
          • [4.1 语句格式](#4.1 语句格式)
          • [4.2 使用说明和注意事项](#4.2 使用说明和注意事项)
          • [4.3 示例](#4.3 示例)
        • [5. MIN() 函数](#5. MIN() 函数)
          • [5.1 语句格式](#5.1 语句格式)
          • [5.2 使用说明和注意事项](#5.2 使用说明和注意事项)
          • [5.3 示例](#5.3 示例)
        • [6. 聚集函数通用注意事项](#6. 聚集函数通用注意事项)
          • [6.1 NULL值处理规则](#6.1 NULL值处理规则)
          • [6.2 DISTINCT与ALL的区别](#6.2 DISTINCT与ALL的区别)
          • [6.3 与GROUP BY结合使用](#6.3 与GROUP BY结合使用)
          • [6.4 与HAVING结合使用](#6.4 与HAVING结合使用)
          • [6.5 使用限制](#6.5 使用限制)
      • 八、集合操作
        • [1. 核心前提条件](#1. 核心前提条件)
        • [2. 详细语法与示例](#2. 详细语法与示例)
          • [2.1 并操作 (UNION)](#2.1 并操作 (UNION))
          • [2.2 交操作 (INTERSECT)](#2.2 交操作 (INTERSECT))
          • [2.3 差操作 (EXCEPT / MINUS)](#2.3 差操作 (EXCEPT / MINUS))
        • [3. 重要注意事项](#3. 重要注意事项)
      • 九、派生表
        • [1. 详细语法与示例](#1. 详细语法与示例)
          • [1.1 基本语法格式](#1.1 基本语法格式)
          • [1.2 使用说明和注意事项](#1.2 使用说明和注意事项)
          • [1.3 示例](#1.3 示例)
        • [2. 重要限制与替代方案](#2. 重要限制与替代方案)
      • 十、其他实用查询
        • [1. 分页查询](#1. 分页查询)
        • [2. CASE表达式](#2. CASE表达式)
      • 十一、查询执行顺序
        • [1. 逻辑执行顺序](#1. 逻辑执行顺序)
        • [2. 书写顺序 vs 执行顺序](#2. 书写顺序 vs 执行顺序)
    • 数据更新
      • 一、插入数据 (INSERT)
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • [二、 更新数据 (UPDATE)](#二、 更新数据 (UPDATE))
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • [三、 删除数据 (DELETE)](#三、 删除数据 (DELETE))
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • [四、 清空表数据 (TRUNCATE)](#四、 清空表数据 (TRUNCATE))
        • [1. 语句格式](#1. 语句格式)
        • [2. 使用说明和注意事项](#2. 使用说明和注意事项)
        • [3. 示例](#3. 示例)
      • 五、空值
        • [1. 空值在各种运算中的行为](#1. 空值在各种运算中的行为)
          • [1.1 算术运算](#1.1 算术运算)
          • [1.2 比较运算与三值逻辑](#1.2 比较运算与三值逻辑)
          • [1.3 聚合函数](#1.3 聚合函数)
          • [1.4 分组与排序](#1.4 分组与排序)
        • [2. 常见陷阱与处理函数](#2. 常见陷阱与处理函数)
          • [2.1 `NOT IN` 子查询中的 `NULL`](#2.1 NOT IN 子查询中的 NULL)
          • [2.2 空值处理函数](#2.2 空值处理函数)
    • 视图
      • [一、 视图的定义](#一、 视图的定义)
      • [二、 视图的基本操作](#二、 视图的基本操作)
        • [1. 创建视图](#1. 创建视图)
        • [2. 查询视图](#2. 查询视图)
        • [3. 修改视图](#3. 修改视图)
        • [4. 删除视图](#4. 删除视图)
      • [三、 视图的受限更新](#三、 视图的受限更新)
      • [四、 定义基于视图的新视图(视图嵌套)](#四、 定义基于视图的新视图(视图嵌套))
      • [三、 视图的受限更新](#三、 视图的受限更新)
      • [四、 定义基于视图的新视图(视图嵌套)](#四、 定义基于视图的新视图(视图嵌套))

数据定义

操作对象 创建 删除 修改
模式 (Schema) CREATE SCHEMA DROP SCHEMA
表 (Table) CREATE TABLE DROP TABLE ALTER TABLE
视图 (View) CREATE VIEW DROP VIEW
索引 (Index) CREATE INDEX DROP INDEX ALTER INDEX(重命名)

一、 模式 (Schema)

模式是一个命名空间,用于组织和隔离数据库对象(如表、视图)。

每个基本表都属于某个模式,一个模式包含多个基本表。

1. 创建模式 (DBA权限)

语法:

sql 复制代码
CREATE SCHEMA [<模式名>] AUTHORIZATION <用户名>;

如果省略模式名,则模式名默认为用户名。

示例:

为用户 ZHANG创建一个名为 TEST的模式。

sql 复制代码
CREATE SCHEMA TEST AUTHORIZATION ZHANG;
2. 删除模式

语法:

sql 复制代码
DROP SCHEMA <模式名> <CASCADE | RESTRICT>;
  • CASCADE(级联):删除模式及其包含的所有对象
  • RESTRICT(限制):只有当模式为空时才允许删除

示例:

删除模式 ZHANG及其所有对象

sql 复制代码
DROP SCHEMA ZHANG CASCADE;

注意:在 MySQL 中,创建模式(CREATE SCHEMA)与创建数据库(CREATE DATABASE)等效。


二、 表 (Table)

表是数据库中存储数据的主要单位。

1. 创建表

语法:

sql 复制代码
CREATE TABLE <表名> (
    <列名> <数据类型> [列级完整性约束],
    ...
    [, <表级完整性约束>]
);
sql 复制代码
CREATE TABLE "<模式名>".<表名> (
    <列名> <数据类型> [列级完整性约束],
    ...
    [, <表级完整性约束>]
);

示例:

创建一个学生表 Student,学号为主键,姓名需唯一

sql 复制代码
CREATE TABLE Student (
    Sno CHAR(9) PRIMARY KEY,     /* 列级约束,Sno是主码 */
    Sname CHAR(20) UNIQUE,        /* Sname取唯一值 */
    Ssex CHAR(2),
    Sage SMALLINT,
    Sdept CHAR(20)
);
2. 修改表

语法:

sql 复制代码
ALTER TABLE <表名>
[ADD [COLUMN] <新列名> <数据类型> [完整性约束]]
[ADD <表级完整性约束>]
[DROP COLUMN <列名> [CASCADE|RESTRICT]]
[DROP CONSTRAINT <完整性约束> [CASCADE|RESTRICT]]
[ALTER COLUMN <列名> <数据类型>];

示例:

Student表增加"入学时间"列,数据类型为日期

默认修改后新添的列为空值(NULL).

sql 复制代码
ALTER TABLE Student ADD S_entrance DATE;
sql 复制代码
ALTER TABLE Student ADD UNIQUE(Cname);
3. 删除表

语法:

sql 复制代码
DROP TABLE <表名> [CASCADE | RESTRICT];

示例:

删除学生表 Student

sql 复制代码
DROP TABLE Student CASCADE;
4. 数据类型与约束条件速查表
类别 名称 说明/关键字 示例
常用数据类型 整型 存储整数,如 INT, BIGINT, SMALLINT Sage SMALLINT
浮点型 存储小数,如 FLOAT(n), DOUBLE, DECIMAL(m,n) price DECIMAL(10, 2)
字符型 存储文本。定长用CHAR(n);变长用VARCHAR(n) Sname CHAR(20)
日期时间型 存储日期或时间,如 DATE, TIME, TIMESTAMP created_at TIMESTAMP
列级完整性约束 主键约束 PRIMARY KEY Sno CHAR(9) PRIMARY KEY
非空约束 NOT NULL Sname VARCHAR(50) NOT NULL
唯一约束 UNIQUE email VARCHAR(100) UNIQUE
检查约束 CHECK Sage INT CHECK (Sage >= 0)
默认约束 DEFAULT created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
表级完整性约束 主键约束(多列) PRIMARY KEY (col1, col2) PRIMARY KEY (Sno, Cno)
外键约束 FOREIGN KEY (col1) REFERENCES Table2(col2) FOREIGN KEY (Sno) REFERENCES Student(Sno)
检查约束(多列) CHECK(涉及多个列的条件) CHECK (Sal + Deduct >= 3000)
4.1 列级完整性约束条件

列级约束直接跟在列定义之后,作用于单个列。

NOT NULL(非空约束)

确保列不能接受NULL值。

sql 复制代码
CREATE TABLE Student (
    Sno CHAR(9) NOT NULL,  -- 学号不能为空
    Sname CHAR(20) NOT NULL  -- 姓名不能为空
);
UNIQUE(唯一约束)

确保某列中的所有值都是不同的。

sql 复制代码
CREATE TABLE Department (
    Dname CHAR(9) UNIQUE NOT NULL  -- 部门名称必须唯一且非空
);
PRIMARY KEY(主键约束)

唯一标识表中的每一行,隐含了NOT NULLUNIQUE的特性。一个表只能有一个主键。

sql 复制代码
CREATE TABLE Student (
    Sno CHAR(9) PRIMARY KEY  -- 学号是主键
);
CHECK(检查约束)

定义列中值必须满足的条件。

sql 复制代码
CREATE TABLE SC (
    Grade SMALLINT CHECK(Grade >= 0 AND Grade <= 100)  -- 成绩必须在0到100之间
);
DEFAULT(默认约束)

当未给列指定值时,自动提供一个默认值。

sql 复制代码
CREATE TABLE Orders (
    OrderDate DATE DEFAULT CURRENT_DATE  -- 默认订单日期为当天
);

4.2 表级完整性约束条件

表级约束在所有列定义之后声明,可以作用于多个列或定义表之间的关系。

PRIMARY KEY(主键,多列/复合主键)

当主键由多个列共同构成时,必须使用表级约束定义。

sql 复制代码
CREATE TABLE SC (
    Sno CHAR(9),
    Cno CHAR(4),
    Grade SMALLINT,
    PRIMARY KEY (Sno, Cno)  -- 学号和课程号共同构成主键
);
FOREIGN KEY(外键约束)

强制表的列(或列组)的值必须匹配另一个表的主键或唯一键的值,以维护参照完整性。还可以定义级联操作(如ON DELETE CASCADE)。

sql 复制代码
CREATE TABLE SC (
    Sno CHAR(9),
    Cno CHAR(4),
    FOREIGN KEY (Sno) REFERENCES Student(Sno),  -- Sno是外键,参照Student表的Sno
    FOREIGN KEY (Cno) REFERENCES Course(Cno)    -- Cno是外键,参照Course表的Cno
);
CHECK(检查约束,表级)

当检查条件涉及多个列时,需要使用表级约束。

sql 复制代码
CREATE TABLE TEACHER (
    Sal NUMERIC(7,2),
    Deduct NUMERIC(7,2),
    CONSTRAINT C1 CHECK (Sal + Deduct >= 3000)  -- 应发工资(Sal+Deduct)不低于3000
);

4.3 综合建表示例

下面是一个融合了多种数据类型和约束的完整示例,模拟一个简单的学生选课系统表结构:

sql 复制代码
-- 创建学生表
CREATE TABLE Student (
    Sno CHAR(9) PRIMARY KEY,              -- 列级主键
    Sname VARCHAR(50) NOT NULL,           -- 非空约束
    Ssex CHAR(2) CHECK (Ssex IN ('男', '女')), -- 检查约束
    Sage SMALLINT CHECK (Sage > 0),       -- 检查约束
    Sdept CHAR(20)
);

-- 创建课程表
CREATE TABLE Course (
    Cno CHAR(4) PRIMARY KEY,
    Cname VARCHAR(100) NOT NULL UNIQUE,  -- 唯一约束
    Ccredit SMALLINT
);

-- 创建选课表(成绩表)
CREATE TABLE SC (
    Sno CHAR(9),
    Cno CHAR(4),
    Grade SMALLINT CHECK (Grade BETWEEN 0 AND 100), -- 检查约束
    PRIMARY KEY (Sno, Cno),               -- 表级复合主键
    FOREIGN KEY (Sno) REFERENCES Student(Sno) ON DELETE CASCADE, -- 表级外键,级联删除
    FOREIGN KEY (Cno) REFERENCES Course(Cno)  -- 表级外键
);

三、 视图 (View)

视图是基于 SQL 查询结果的虚拟表,简化复杂查询并提供逻辑数据独立性。

1. 创建视图

语法:

sql 复制代码
CREATE VIEW <视图名> [(<列名列表>)]
AS <SELECT查询子句>;

示例:

创建名为 IS_Student的视图,显示信息系(IS)学生的学号、姓名和年龄

sql 复制代码
CREATE VIEW IS_Student AS
SELECT Sno, Sname, Sage
FROM Student
WHERE Sdept = 'IS';
2. 删除视图

语法:

sql 复制代码
DROP VIEW <视图名>;

示例:

删除视图 IS_Student

sql 复制代码
DROP VIEW IS_Student;

四、 索引 (Index)

索引用于加速数据检索,但会增加数据库的存储和维护开销。

DBA或表的属主可以建立索引,DBMS自动建立PRIMMARY KEY,UNIQUE的索引,DBMS自动维护索引。

属于内模式的范畴

1. 创建索引

语法:

sql 复制代码
CREATE [UNIQUE] [CLUSTER] INDEX <索引名>
ON <表名> (<列名> [次序] [, ...]);
  • UNIQUE表示唯一索引

  • CLUSTER表示聚簇索引

    聚簇索引指索引顺序和表中记录的物理顺序一致的索引组织。

    一个基本表只能有一个聚簇索引。

示例:

在选课表 SC上,按学号升序和课程号降序建立唯一索引

sql 复制代码
CREATE UNIQUE INDEX SCno ON SC(Sno ASC, Cno DESC);
2. 删除索引

语法:

sql 复制代码
DROP INDEX <索引名>;

示例:

删除 SC表上的 SCno索引

sql 复制代码
DROP INDEX SCno;

数据查询

一、SELECT 基本查询结构

1. 语句格式
sql 复制代码
SELECT [ALL|DISTINCT] <目标列表达式> [AS <列别名>] [,<目标列表达式>]...
FROM <表名或视图名> [,<表名或视图名>]...
[WHERE <条件表达式>]
[GROUP BY <列名1> [HAVING <条件表达式>]]
[ORDER BY <列名2> [ASC|DESC]];

ASC :升序排列(默认)
DESC:降序排列
ALL 全部行

DISTINCT 消去重复的行
AS可以省略。

2. 示例
sql 复制代码
-- 查询所有学生信息
SELECT * FROM Students;

-- 查询特定列,去除重复
SELECT DISTINCT Department FROM Students;

-- 完整的查询语句
SELECT Department, AVG(Score) AS AvgScore
FROM Students
WHERE Age >= 18
GROUP BY Department
HAVING AVG(Score) >= 60
ORDER BY AvgScore DESC;

二、WHERE 条件查询

1. 语句格式
sql 复制代码
WHERE <条件表达式>
2. 使用说明和注意事项
  • 比较运算符=, <>, !=, >, <, >=, <=
  • 逻辑运算符AND OR NOT

优先级:NOT>AND>OR,可以使用括号改变优先级。

  • 模糊匹配LIKE NOT LIKE 配合 %(任意字符), _(单个字符),\(转义字符)
  • 范围判断BETWEEN ... AND ... NOT BETWEEEN
  • 列表判断IN (value1, value2, ...) NOT IN
  • 空值判断IS NULL, IS NOT NULL
  • BETWEEN包含边界值,等价于列 >= 值1 AND 列 <= 值2
  • IN等价于多个OR条件的组合

字符串使用单引号。

3. 示例
sql 复制代码
-- 基本条件查询
SELECT * FROM Employees WHERE Salary > 5000;

-- 组合条件
SELECT * FROM Students 
WHERE Department = '计算机' AND Grade >= 60;

-- 模糊查询
SELECT * FROM Customers WHERE Name LIKE '张%';

-- 范围查询
SELECT * FROM Products WHERE Price BETWEEN 100 AND 500;

-- 空值查询
SELECT * FROM Users WHERE Email IS NULL;

三、GROUP BY 分组查询

1. 语句格式
sql 复制代码
GROUP BY <列名> [HAVING <条件表达式>]
2. 使用说明和注意事项
  • GROUP BY将行分组,以便对每组进行聚合计算

    后面列名只可以是当前分组对象列名或聚合函数。

  • 常用的聚合函数: COUNT():计数 SUM():求和 AVG():平均值 MAX():最大值 MIN():最小值

  • HAVING用于对分组结果进行筛选

    WHERE 在分组前过滤,HAVING在分组后过滤

    HAVING 中可以使用聚合函数,WHERE中不可以

  • 分组后,SELECT子句中只能出现分组条件列和聚合函数

3. 示例
sql 复制代码
-- 按部门统计平均工资
SELECT Department, AVG(Salary) AS AvgSalary
FROM Employees
GROUP BY Department;

-- 使用HAVING筛选分组结果
SELECT Department, COUNT(*) AS EmpCount
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 10;

-- 多列分组
SELECT Year, Month, SUM(Sales) AS TotalSales
FROM SalesRecords
GROUP BY Year, Month;

四、ORDER BY 排序查询

1. 语句格式
sql 复制代码
ORDER BY <列名> [ASC|DESC] [, <列名> [ASC|DESC]]...
2. 使用说明和注意事项
  • ASC:升序排列(默认)

    空值默认最大值。

  • DESC:降序排列

  • 可以按多列排序,优先级从左到右

  • 可以使用列名、列别名或列序号(从1开始)

  • ORDER BY不能按ntexttextimage数据类型进行排序

3. 示例
sql 复制代码
-- 单列排序
SELECT * FROM Students ORDER BY Score DESC;

-- 多列排序
SELECT * FROM Employees 
ORDER BY Department ASC, Salary DESC;

-- 使用列序号排序
SELECT Name, Age, Score FROM Students
ORDER BY 3 DESC, 2 ASC;  -- 先按Score降序,再按Age升序

五、表连接查询 (JOIN)

1. 语句格式
1.1 等值连接
sql 复制代码
FROM 表1
[INNER|LEFT|RIGHT|FULL] JOIN 表2 ON 连接条件
sql 复制代码
SELECT 列名列表
FROM 表1, 表2
WHERE 表1.列名 比较运算符 表2.列名;
sql 复制代码
SELECT 列名列表
FROM 表1, 表2, 表3
WHERE 表1.列名 BETWEEN 表2.列名 AND 表3.列名;

两个表中独立拥有的列可以单独列出来,不用给出表名。

1.2 自连接
sql 复制代码
SELECT FIRST.Cno,SECOND.Cpno
FROM Course FIRST,Course SECOND
WHERE FIRST.Cpno=SECOND.Cno;
2. 使用说明和注意事项
  • 内连接 (INNER JOIN):返回两个表匹配的行
  • 左连接 (LEFT JOIN):返回左表所有行 + 右表匹配的行
  • 右连接 (RIGHT JOIN):返回右表所有行 + 左表匹配的行
  • 全连接 (FULL JOIN):返回两个表的所有行
  • 连接条件通常是等值连接(=),也可以是其他比较运算符
  • 避免笛卡尔积(不加连接条件)
  • 连接可以在SELECT语句的FROM子句或WHERE子句中建立
3. 示例
sql 复制代码
-- 内连接
SELECT e.Name, d.DepartmentName
FROM Employees e
INNER JOIN Departments d ON e.DepartmentID = d.DepartmentID;

-- 左连接
SELECT s.Name, c.Score
FROM Students s
LEFT JOIN CourseScores c ON s.StudentID = c.StudentID;

-- 多表连接
SELECT o.OrderID, c.Name, p.ProductName
FROM Orders o
JOIN Customers c ON o.CustomerID = c.CustomerID
JOIN OrderDetails od ON o.OrderID = od.OrderID
JOIN Products p ON od.ProductID = p.ProductID;

六、子查询

1. 语句格式
sql 复制代码
WHERE 列 运算符 (SELECT 语句)
2. 使用说明和注意事项
  • 子查询(嵌套查询)是查询中又包含查询
  • 常用运算符:=, >, <, IN, EXISTS, ANY, ALL
  • 子查询必须用括号括起来
  • 子查询可以出现在SELECT, FROM, WHERE, HAVING子句中
  • 相关子查询:子查询引用外层查询的列
  • 非相关子查询:子查询独立于外层查询
  • EXISTS引出的子查询,其目标列表达式通常都用*

下面这个表格汇总了 SQL 中 IN, EXISTS, ANY/SOME, ALL 这几个关键操作符的核心特性和区别。

特性 IN EXISTS ANY/SOME ALL
核心功能 检查某个值是否存在于指定的值列表或子查询结果中。 检查子查询是否返回至少一行结果。关注存在性。 将值与子查询返回的任一值进行比较。 将值与子查询返回的所有值进行比较。
返回值 布尔值(TRUE/FALSE) 布尔值(TRUE/FALSE) 布尔值(TRUE/FALSE) 布尔值(TRUE/FALSE)
子查询结果 返回单列多行。 不关心返回的列和具体值,只关心是否有行返回。通常使用 SELECT 1SELECT * 返回单列多行。 返回单列多行。
NOT组合 NOT IN:不在集合中。需注意NULL值带来的逻辑陷阱。 NOT EXISTS:子查询未返回任何行。 通常不与NOT直接连用,而是使用 <> ANY 等比较运算符表达否定逻辑。 通常不与NOT直接连用,而是使用 <> ALL 等比较运算符表达否定逻辑。
2.1 IN 操作符

IN 操作符用于判断某个字段的值是否属于一个指定的集合,这个集合可以是离散的值列表,也可以是一个子查询返回的结果集 。

  • 基本语法格式

    sql 复制代码
    SELECT column_name(s)
    FROM table_name
    WHERE column_name IN (value1, value2, ...);
    
    -- 或使用子查询
    SELECT column_name(s)
    FROM table_name
    WHERE column_name IN (subquery);
  • 使用说明和注意事项

    • 如果值列表或子查询返回的结果集中包含目标值,则条件为真 。
    • NOT IN 则相反,表示不在指定的集合中。
    • 使用 NOT IN 时需要特别注意:如果子查询返回的结果集中包含 NULL 值,则整个 NOT IN 条件的结果可能是 UNKNOWN,导致查询结果不如预期 。
  • 示例

    sql 复制代码
    -- 查询在'北京'或'上海'的客户
    SELECT cust_name FROM customers WHERE cust_city IN ('北京', '上海') ;
    
    -- 查询有订单的客户ID(使用子查询)
    SELECT cust_id FROM customers
    WHERE cust_id IN (SELECT DISTINCT cust_id FROM orders) ;
    
    -- 使用 NOT IN
    SELECT * FROM products
    WHERE product_id NOT IN (SELECT product_id FROM orderitems WHERE discount >= 0.25) ;
2.2 EXISTS 操作符

EXISTS 用于测试子查询是否返回了任何行。它不关心子查询返回的具体数据内容,只关心是否存在满足条件的记录 。

  • 基本语法格式

    sql 复制代码
    SELECT column_name(s)
    FROM table_name
    WHERE EXISTS (subquery);
  • 使用说明和注意事项

    • 子查询不返回具体数据,通常使用 SELECT 1SELECT *,因为数据库引擎只检查是否存在行 。
    • 一旦子查询找到一条匹配的记录,就会立即返回真,这可能带来性能优势 。
    • EXISTS 常用于相关子查询,即子查询的查询条件依赖于外部查询的字段值 。
    • NOT EXISTS 的作用与 EXISTS 相反。
  • 示例

    sql 复制代码
    -- 查询有订单的客户信息(使用相关子查询)
    SELECT cust_name, cust_city
    FROM customers c
    WHERE EXISTS (SELECT 1 FROM orders o WHERE o.cust_id = c.cust_id) ;
    
    -- 查询没有成绩记录的学生
    SELECT sname, sage FROM student s
    WHERE NOT EXISTS (SELECT 1 FROM score WHERE sid = s.sid) ;

通过 EXISTSNOT EXISTS实现全称量词和逻辑蕴含是 SQL 查询中的重要技巧。下面我们通过具体示例来详细说明如何实现。

使用 EXISTS 实现全称量词

SQL 中没有直接的全称量词(for all),但可以通过"双重否定"的逻辑转换来实现:"所有 x 都满足条件 P"等价于"不存在 x 不满足条件 P"

示例:查询选修了全部课程的学生姓名

这个查询可以转换为:查找这样的学生,没有一门课程是他不选修的

sql 复制代码
SELECT Sname
FROM Student
WHERE NOT EXISTS (
    SELECT *
    FROM Course
    WHERE NOT EXISTS (
        SELECT *
        FROM SC
        WHERE Sno = Student.Sno 
          AND Cno = Course.Cno
    )
);

执行过程分析

  1. 从外层 Student表取出一个学生。
  2. 在中间层 Course表中,遍历每一门课程。
  3. 在最内层查询中,检查该学生是否选修了当前课程。
  4. 如果存在一门课程,该学生没有选修(最内层查询结果为空,NOT EXISTS返回真),则中间层查询结果非空,导致外层 NOT EXISTS返回假,该学生不被选中。
  5. 只有当该学生选修了所有课程(即对每门课程,最内层查询都能找到记录,使得中间层的 NOT EXISTS结果始终为假,最终外层 NOT EXISTS为真),该学生才会被包含在结果中。
使用 EXISTS 实现逻辑蕴含

逻辑蕴含(p → q)可以转换为:¬p ∨ q ,进而可以表述为 "不存在满足 p 但不满足 q 的情况"

示例:查询至少选修了学号为 '201215122' 的学生选修的全部课程的学生号码

这个查询的含义是:不存在这样的课程,学号 '201215122' 选修了,而当前考察的学生却没有选修

sql 复制代码
SELECT DISTINCT Sno
FROM SC SCX
WHERE NOT EXISTS (
    SELECT *
    FROM SC SCY
    WHERE SCY.Sno = '201215122'
      AND NOT EXISTS (
          SELECT *
          FROM SC SCZ
          WHERE SCZ.Sno = SCX.Sno 
            AND SCZ.Cno = SCY.Cno
      )
);

执行过程分析

  1. 从外层 SCX表中取出一个学生(代表待检查的学生x)。
  2. 在中间层查询中,找出学号 '201215122' 选修的所有课程。
  3. 对于 '201215122' 选修的每一门课程,在最内层查询中检查学生x是否也选修了该课程。
  4. 如果存在一门课程,'201215122' 选修了(SCY.Sno = '201215122'为真),但学生x没有选修(最内层查询结果为空,NOT EXISTS返回真),那么中间层查询结果非空,导致外层 NOT EXISTS返回假,该学生x不被选中。
  5. 只有当学生x选修了 '201215122' 所选修的每一门课程(即对 '201215122' 选修的每门课,最内层查询都能找到记录,使得中间层的 NOT EXISTS结果始终为假,最终外层 NOT EXISTS为真),学生x才会被包含在结果中。
核心思路与技巧
  1. 双重否定是关键 :无论是全称量词还是逻辑蕴含,核心思路都是利用 NOT EXISTS进行双重否定来表达肯定含义。
  2. 相关子查询 :这些查询通常都是相关子查询,内层查询依赖于外层查询的当前行值。
  3. EXISTS的特性EXISTS子查询只关心是否存在匹配的行(返回真值),而不关心具体内容,因此其目标列通常使用 *
  4. 逻辑转换 : 全称量词 ∀ x P ( x ) ∀x P(x) ∀xP(x) ⇒ 使用 ¬ ∃ x ^¬∃x ¬∃x ¬ P ( x ) ^¬P(x) ¬P(x) 实现。 逻辑蕴含 p → q ⇒ 使用 ¬ ( p ∧ ¬ q ) ^¬(p ∧ ¬q) ¬(p∧¬q) 实现,进而用 NOT EXISTS表达 ¬ ∃ ( p ∧ ¬ q ) ^¬∃(p ∧ ¬q) ¬∃(p∧¬q)。

掌握这些转换技巧和嵌套 EXISTS的使用,能够帮助你解决 SQL 查询中许多复杂的逻辑问题。

2.3 ANY/SOME 操作符

ANYSOME 是同义词,用于将某个值与子查询结果集中的任意一个值 进行比较 。必须与比较运算符(如 =, >, < 等)一起使用。

  • 基本语法格式

    sql 复制代码
    SELECT column_name(s)
    FROM table_name
    WHERE column_name operator ANY (subquery);
    -- SOME 用法与 ANY 完全相同
  • 使用说明和注意事项

    • operator 可以是 =, >, >=, <, <=, <> 等。
    • = ANY 的功能与 IN 操作符等效 。
    • 只要与子查询结果中的任何一个值比较条件为真,整个表达式就返回真。
  • 示例

    sql 复制代码
    -- 查询工资高于任何一位在'IT'部门的员工的员工信息(等价于高于IT部门最低工资)
    SELECT emp_name, salary FROM employees
    WHERE salary > ANY (SELECT salary FROM employees WHERE department = 'IT') ;
    
    -- 查询工作和'IT'部门任意员工相同的员工信息(等价于 IN)
    SELECT emp_name, job FROM employees
    WHERE job = ANY (SELECT job FROM employees WHERE department = 'IT') ;
2.4 ALL 操作符

ALL 操作符用于将某个值与子查询结果集中的每一个值进行比较 。同样必须与比较运算符一起使用。

  • 基本语法格式

    sql 复制代码
    SELECT column_name(s)
    FROM table_name
    WHERE column_name operator ALL (subquery);
  • 使用说明和注意事项

    • 必须与子查询返回的所有值比较条件都为真,整个表达式才为真 。
    • <> ALL 的功能与 NOT IN 等效(但需要注意 NOT INNULL 值问题)。
  • 示例

    sql 复制代码
    -- 查询工资高于'销售'部门所有员工的员工信息(等价于高于销售部门最高工资)
    SELECT emp_name, salary FROM employees
    WHERE salary > ALL (SELECT salary FROM employees WHERE department = 'Sales') ;
    
    -- 查询工资不等于'经理'岗位任何工资的员工(使用 <> ALL)
    SELECT emp_name FROM employees
    WHERE salary <> ALL (SELECT DISTINCT salary FROM employees WHERE job_title = '经理') ;
查询需求 使用 ANY / SOME 使用 ALL 等价聚集函数写法 等价 IN 谓词写法 核心逻辑解释
等于集合中某个值 = ANY(subquery) -- -- IN (subquery) 判断值是否在子查询返回的集合中。
不等于集合中所有值 -- <> ALL(subquery) -- NOT IN (subquery) 判断值是否不在子查询返回的集合中(需注意 NULL 值的影响)。
大于集合中最小值 > ANY(subquery) -- > (SELECT MIN(...)) -- 只要大于集合里的最小值即可满足条件。
大于集合中最大值 -- > ALL(subquery) > (SELECT MAX(...)) -- 必须大于集合里的最大值才能满足条件。
小于集合中最大值 < ANY(subquery) -- < (SELECT MAX(...)) -- 只要小于集合里的最大值即可满足条件。
小于集合中最小值 -- < ALL(subquery) < (SELECT MIN(...)) -- 必须小于集合里的最小值才能满足条件。
  • 慎用 NOT IN<> ALL 在逻辑上等价于 NOT IN,但使用 NOT IN 时需要特别小心。如果子查询返回的结果集中包含 NULL ,那么整个 NOT IN 条件的结果将是 UNKNOWN (未知),可能导致查询结果不返回任何行,这与直觉不符。更安全的做法是使用 NOT EXISTS 或在子查询中排除 NULL 值。
  • 空子查询的情况
    • 如果 ALL 对应的子查询没有返回任何行,ALL 会返回 TRUE。因为逻辑是"对于所有行,比较都成立",而既然没有行需要比较,这个条件被视为"空真"。
    • 如果 ANY 对应的子查询没有返回任何行,ANY 会返回 FALSE。因为"存在一个行满足条件"的要求失败了。
3. 示例

可以理解为双层循环查找,Student为外层,SC为内层。

sql 复制代码
-- WHERE子句中的子查询
SELECT * FROM Employees
WHERE Salary > (SELECT AVG(Salary) FROM Employees);

-- IN运算符子查询
SELECT * FROM Students
WHERE ClassID IN (SELECT ClassID FROM Classes WHERE Year = 2023);

-- FROM子句中的子查询(派生表)
SELECT dept_stats.*
FROM (
    SELECT Department, COUNT(*) AS EmpCount, AVG(Salary) AS AvgSalary
    FROM Employees
    GROUP BY Department
) AS dept_stats
WHERE dept_stats.AvgSalary > 5000;

-- EXISTS子查询
SELECT first_name, last_name
FROM employees
WHERE EXISTS (SELECT 1 FROM departments 
              WHERE departments.department_id = employees.department_id 
              AND departments.department_name = 'Sales');

七、聚集函数

1. COUNT() 函数
1.1 语句格式
sql 复制代码
COUNT([DISTINCT|ALL] <列名> | *)
1.2 使用说明和注意事项
  • 功能:统计行数或非NULL值的数量
  • 参数选项*:统计表中的总行数(包含NULL值) ALL 列名:统计指定列中非NULL值的数量(默认) DISTINCT 列名:统计指定列中不同非NULL值的数量
  • 返回类型:整数(BIGINT)
  • NULL值处理:忽略NULL值(除非使用COUNT(*))
1.3 示例
sql 复制代码
-- 统计总行数
SELECT COUNT(*) AS TotalRows FROM Students;

-- 统计非NULL值的数量
SELECT COUNT(Sname) AS NameCount FROM Students;

-- 统计不同值的数量
SELECT COUNT(DISTINCT Sdept) AS DeptCount FROM Students;
2. SUM() 函数
2.1 语句格式
sql 复制代码
SUM([DISTINCT|ALL] <列名>)
2.2 使用说明和注意事项
  • 功能:计算数值列的总和
  • 参数选项ALL 列名:计算所有非NULL值的总和(默认) DISTINCT 列名:计算不同非NULL值的总和
  • 返回类型:与列数据类型相同或更高精度
  • NULL值处理:忽略NULL值
  • 适用数据类型:只能用于数值类型(INT, DECIMAL, FLOAT等)
2.3 示例
sql 复制代码
-- 计算总成绩
SELECT SUM(Score) AS TotalScore FROM SC;

-- 计算不同成绩的总和
SELECT SUM(DISTINCT Score) AS DistinctScoreSum FROM SC;

-- 结合WHERE条件
SELECT SUM(Amount) AS TotalAmount FROM Orders WHERE Status = '已完成';
3. AVG() 函数
3.1 语句格式
sql 复制代码
AVG([DISTINCT|ALL] <列名>)
3.2 使用说明和注意事项
  • 功能:计算数值列的平均值
  • 参数选项ALL 列名:计算所有非NULL值的平均值(默认) DISTINCT 列名:计算不同非NULL值的平均值
  • 返回类型:浮点数(通常是DECIMAL或DOUBLE)
  • NULL值处理:忽略NULL值
  • 适用数据类型:只能用于数值类型
3.3 示例
sql 复制代码
-- 计算平均成绩
SELECT AVG(Score) AS AverageScore FROM SC;

-- 计算不同成绩的平均值
SELECT AVG(DISTINCT Score) AS DistinctAvgScore FROM SC;

-- 按组计算平均值
SELECT Sdept, AVG(Sage) AS AvgAge FROM Student GROUP BY Sdept;
4. MAX() 函数
4.1 语句格式
sql 复制代码
MAX([DISTINCT|ALL] <列名>)
4.2 使用说明和注意事项
  • 功能:返回列中的最大值
  • 参数选项ALL 列名:返回所有非NULL值的最大值(默认) DISTINCT 列名:返回不同非NULL值的最大值(与ALL结果相同)
  • NULL值处理:忽略NULL值
  • 适用数据类型:可用于数值、字符、日期等可比较类型
  • 字符比较:按字符编码顺序比较
4.3 示例
sql 复制代码
-- 获取最高工资
SELECT MAX(Salary) AS MaxSalary FROM Employees;

-- 获取最晚入职日期
SELECT MAX(HireDate) AS LatestHireDate FROM Employees;

-- 按部门获取最高工资
SELECT Department, MAX(Salary) AS MaxSalary 
FROM Employees 
GROUP BY Department;
5. MIN() 函数
5.1 语句格式
sql 复制代码
MIN([DISTINCT|ALL] <列名>)
5.2 使用说明和注意事项
  • 功能:返回列中的最小值
  • 参数选项ALL 列名:返回所有非NULL值的最小值(默认) DISTINCT 列名:返回不同非NULL值的最小值(与ALL结果相同)
  • NULL值处理:忽略NULL值
  • 适用数据类型:可用于数值、字符、日期等可比较类型
  • 字符比较:按字符编码顺序比较
5.3 示例
sql 复制代码
-- 获取最低工资
SELECT MIN(Salary) AS MinSalary FROM Employees;

-- 获取最早入职日期
SELECT MIN(HireDate) AS EarliestHireDate FROM Employees;

-- 按课程获取最低分
SELECT Cno, MIN(Score) AS MinScore 
FROM SC 
GROUP BY Cno;
6. 聚集函数通用注意事项
6.1 NULL值处理规则
函数 处理方式
COUNT(*) 包含NULL值
COUNT(列名) 忽略NULL值
SUM(列名) 忽略NULL值
AVG(列名) 忽略NULL值
MAX(列名) 忽略NULL值
MIN(列名) 忽略NULL值
6.2 DISTINCT与ALL的区别
sql 复制代码
-- 假设Score列值为:90, 90, 85, NULL
SELECT AVG(ALL Score) FROM SC;       -- 结果为(90+90+85)/3 = 88.33
SELECT AVG(DISTINCT Score) FROM SC;  -- 结果为(90+85)/2 = 87.50
6.3 与GROUP BY结合使用
sql 复制代码
-- 按部门统计各项指标
SELECT Sdept, 
       COUNT(*) AS StudentCount,
       AVG(Sage) AS AvgAge,
       MAX(Sage) AS MaxAge,
       MIN(Sage) AS MinAge
FROM Student
GROUP BY Sdept;
6.4 与HAVING结合使用
sql 复制代码
-- 筛选平均成绩大于80的课程
SELECT Cno, AVG(Score) AS AvgScore
FROM SC
GROUP BY Cno
HAVING AVG(Score) > 80;
6.5 使用限制
  • 聚集函数不能直接在WHERE子句中使用(但可以在HAVING子句中使用)
  • SELECT列表中,非聚集函数的列必须出现在GROUP BY子句中
  • 聚集函数可以嵌套使用,但通常需要配合子查询
sql 复制代码
-- 错误示例:聚集函数不能在WHERE中直接使用
SELECT * FROM Students WHERE Score > AVG(Score);  -- 错误!

-- 正确示例:使用子查询
SELECT * FROM Students 
WHERE Score > (SELECT AVG(Score) FROM Students);
sql 复制代码
-- 统计各系学生人数、平均年龄、最大最小年龄
SELECT Sdept AS 系别,
       COUNT(*) AS 人数,
       AVG(Sage) AS 平均年龄,
       MAX(Sage) AS 最大年龄,
       MIN(Sage) AS 最小年龄,
       COUNT(DISTINCT Ssex) AS 性别种类数
FROM Student
GROUP BY Sdept
HAVING COUNT(*) > 10  -- 只显示人数大于10的系
ORDER BY AVG(Sage) DESC;

八、集合操作

SQL的集合查询允许你将多个SELECT语句的结果进行类似数学集合的并、交、差操作,这对于数据对比和整合非常有用。

下面这个表格汇总了这些操作的核心特性和区别。

操作类型 关键字 功能描述 类比数学符号
并操作 UNION 合并两个查询结果,自动去除重复行。 ∪ (并集)
UNION ALL 合并两个查询结果,保留所有行(包括重复行)。 -
交操作 INTERSECT 返回两个查询结果中共有的行。 ∩ (交集)
差操作 EXCEPT (或 MINUS) 返回在第一个查询结果中但不在第二个查询结果中的行。 − (差集)
1. 核心前提条件

进行集合查询前,务必确保所有SELECT语句满足以下两点,否则会执行错误 :

  1. 列数相同 :所有SELECT语句查询的列数必须相等。
  2. 类型兼容 :对应列的数据类型必须兼容(如INTDECIMALVARCHARTEXT)。

2. 详细语法与示例
2.1 并操作 (UNION)

用于垂直合并多个查询结果。

  • 语句格式

    sql 复制代码
    -- 合并结果并去重
    SELECT column1, column2, ... FROM table1 [WHERE conditions]
    UNION
    SELECT column1, column2, ... FROM table2 [WHERE conditions];
    
    -- 合并结果并保留所有记录
    SELECT column1, column2, ... FROM table1 [WHERE conditions]
    UNION ALL
    SELECT column1, column2, ... FROM table2 [WHERE conditions];
  • 使用说明和注意事项

    • UNION 会去除最终结果中的重复行,而 UNION ALL 则保留所有行,包括重复的 。
    • UNION ALL 的性能通常优于 UNION,因为省去了去重的步骤。在确认结果不会有重复或不在意重复时,建议使用 UNION ALL
  • 示例

    sql 复制代码
    -- 查询学校中所有师生的姓名(使用UNION自动去重)
    SELECT Sname AS Name FROM Student
    UNION
    SELECT Tname FROM Teacher; 
    
    -- 合并两个区域的销售数据,保留所有记录以进行准确汇总
    SELECT product_id, amount FROM north_sales
    UNION ALL
    SELECT product_id, amount FROM south_sales; 
2.2 交操作 (INTERSECT)

用于找出多个查询结果中共有的行。

  • 语句格式

    sql 复制代码
    SELECT column1, column2, ... FROM table1 [WHERE conditions]
    INTERSECT
    SELECT column1, column2, ... FROM table2 [WHERE conditions];
  • 使用说明和注意事项

    • 一些数据库(如 MySQL 5.7 及以下版本 )不直接支持 INTERSECT,需要用其他方法模拟 。
    • 常见的替代方法是使用 IN 子查询或 EXISTS 子查询 。
  • 示例

    sql 复制代码
    -- (在支持INTERSECT的数据库中) 查询既是计算机专业,又年龄≤19岁的学生
    SELECT * FROM Student WHERE Smajor='计算机科学与技术'
    INTERSECT
    SELECT * FROM Student WHERE Sage<=19; 
    
    -- (在MySQL等数据库中) 使用IN子查询实现相同功能:查询即选修了课程1又选修了课程2的学生
    SELECT Sno
    FROM SC
    WHERE Cno = '1'
      AND Sno IN (SELECT Sno FROM SC WHERE Cno = '2'); 
2.3 差操作 (EXCEPT / MINUS)

用于找出存在于第一个查询结果但不存在于后续查询结果中的行。

  • 语句格式

    sql 复制代码
    -- 标准SQL中使用 EXCEPT
    SELECT column1, column2, ... FROM table1 [WHERE conditions]
    EXCEPT
    SELECT column1, column2, ... FROM table2 [WHERE conditions];
    
    -- Oracle中使用 MINUS
    SELECT column1, column2, ... FROM table1 [WHERE conditions]
    MINUS
    SELECT column1, column2, ... FROM table2 [WHERE conditions]; 
  • 使用说明和注意事项

    • 差集操作是顺序敏感 的,A EXCEPT BB EXCEPT A 的结果通常不同 。
    • 与交操作类似,MySQL 5.7 及以下版本 不直接支持 EXCEPT,需要使用 LEFT JOIN ... IS NULLNOT EXISTS 等方法实现 。
  • 示例

    sql 复制代码
    -- (在支持EXCEPT的数据库中) 查询计算机专业,但年龄>19岁的学生
    SELECT * FROM Student WHERE Smajor='计算机科学与技术'
    EXCEPT
    SELECT * FROM Student WHERE Sage<=19; 
    
    -- (在MySQL等数据库中) 使用NOT EXISTS实现相同功能:查询已注册但未下单的用户
    SELECT u.user_id
    FROM users u
    WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id); 

3. 重要注意事项

排序操作的位置

对最终集合结果排序时,ORDER BY 子句只能出现在最后一个 SELECT 语句之后 ,并且排序列应使用第一个 SELECT 中的列名或列序号 。

sql 复制代码
-- 正确写法
SELECT Sname, Sage FROM Student WHERE Sdept = 'CS'
UNION
SELECT Sname, Sage FROM Student WHERE Sage <= 19
ORDER BY Sage DESC;  -- 或者 ORDER BY 2 DESC 

九、派生表

SQL 中的派生表(Derived Table)是写在 FROM 子句中的子查询,它像一个临时的、匿名的结果集,可以帮助您将复杂的查询分解成更易管理和理解的步骤。

下面这个表格汇总了派生表的核心语法要点。

特性 说明/规则
基本语法 SELECT ... FROM (subquery) AS alias_name ...
别名(AS) 必须为派生表指定一个别名。
列名 派生表中的每一列都必须有唯一名称 。可在子查询内用AS定义,或在派生表别名后直接定义。
ORDER BY 通常不能在派生表中使用ORDER BY,除非与TOPLIMIT等一起使用。排序应在外部查询进行。
1. 详细语法与示例
1.1 基本语法格式

派生表的核心是在 FROM 子句中放置一个用括号括起来的子查询,并为其指定一个别名。

sql 复制代码
SELECT outer_query_columns
FROM (
    SELECT inner_query_columns
    FROM ...
    [WHERE ...]
    [GROUP BY ...]
) AS derived_table_alias
[WHERE outer_query_conditions];
1.2 使用说明和注意事项
  • 临时性:派生表仅在查询执行期间存在,查询结束后自动销毁,不存储在数据库中。
  • 作用域:派生表只能在定义它的外部查询中访问。外部查询结束后,派生表便不可用。
  • 性能:派生表主要用于逻辑结构优化,通常不会直接提升查询性能。
1.3 示例

下面通过几个典型场景展示派生表的应用。

示例1:在聚合函数上再进行过滤

直接编写 HAVING AVG(SUM(column1)) 是错误的。通过派生表可以先计算分组聚合,外部查询再对聚合结果进行过滤。

sql 复制代码
-- 目标:计算每个部门的总工资,然后找出总工资超过10万的部门
SELECT department_name, total_salary
FROM (
    SELECT d.department_name, SUM(e.salary) AS total_salary
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    GROUP BY d.department_name
) AS dept_totals  -- 派生表,计算每个部门的总工资
WHERE total_salary > 100000;  -- 外部查询过滤

示例2:避免重复计算,提升可读性

当需要在多个地方使用相同的子查询结果时,派生表能避免代码重复。

sql 复制代码
-- 目标:查询每个员工的姓名、工资、所在部门及该部门的平均工资
SELECT e.name, e.salary, d.department_name, dept_avg.avg_salary
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN (
    -- 派生表:计算每个部门的平均工资
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
) AS dept_avg ON e.department_id = dept_avg.department_id;

示例3:实现复杂的多步查询

对于需要多个步骤的复杂查询,派生表可以使逻辑更清晰。

sql 复制代码
-- 目标:找出每个学生超过他自己选修课程平均成绩的课程号
SELECT sc.sno, sc.cno
FROM sc
JOIN (
    -- 派生表:计算每个学生的平均成绩
    SELECT sno, AVG(grade) AS avg_grade
    FROM sc
    GROUP BY sno
) AS avg_sc ON sc.sno = avg_sc.sno
WHERE sc.grade >= avg_sc.avg_grade;  -- 对比成绩
2. 重要限制与替代方案
  1. 列别名的作用域:在派生表子查询中定义的列别名,可以在外部查询中直接使用。

    sql 复制代码
    -- 错误:不能在WHERE子句中直接使用SELECT中定义的别名
    SELECT YEAR(orderdate) AS orderyear FROM Sales.Orders GROUP BY orderyear;
    
    -- 正确:使用派生表
    SELECT orderyear
    FROM (
        SELECT YEAR(orderdate) AS orderyear
        FROM Sales.Orders
    ) AS O1
    GROUP BY orderyear;  -- 这里可以引用派生表内的列别名
  2. 替代方案:公用表表达式(CTE) :对于复杂的、需要多次引用或递归的查询,可读性更好的 公用表表达式(CTE) 是很好的选择。

    sql 复制代码
    -- 使用CTE重写示例2,逻辑更清晰
    WITH DepartmentAverages AS (
        SELECT department_id, AVG(salary) AS avg_salary
        FROM employees
        GROUP BY department_id
    )
    SELECT e.name, e.salary, d.department_name, da.avg_salary
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    JOIN DepartmentAverages da ON e.department_id = da.department_id;

十、其他实用查询

1. 分页查询
sql 复制代码
-- MySQL语法
SELECT * FROM Employees LIMIT 10;  -- 前10条
SELECT * FROM Employees LIMIT 10 OFFSET 20;  -- 第21-30条

-- SQL Server语法
SELECT TOP 10 * FROM Employees;
SELECT * FROM Employees OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

-- 分页公式:起始索引 = (当前页码 - 1) × 每页条数
SELECT * FROM Student ORDER BY Sage LIMIT 0, 2;  -- 从0开始取2条数据
2. CASE表达式
sql 复制代码
SELECT Name, Score,
    CASE
        WHEN Score >= 90 THEN '优秀'
        WHEN Score >= 80 THEN '良好'
        WHEN Score >= 60 THEN '及格'
        ELSE '不及格'
    END AS GradeLevel
FROM Students;

十一、查询执行顺序

1. 逻辑执行顺序

SQL查询的逻辑执行顺序为(与实际书写顺序不同):

  1. FROM:确定数据源表并执行笛卡尔积
  2. ON:应用连接条件,生成连接结果
  3. OUTER JOIN:添加外部行(如适用)
  4. WHERE:行级过滤
  5. GROUP BY:分组
  6. HAVING:分组后过滤
  7. SELECT:选择列并计算表达式
  8. DISTINCT:去重
  9. ORDER BY:排序
  10. LIMIT/OFFSET:分页
2. 书写顺序 vs 执行顺序
sql 复制代码
-- 书写顺序(人为编写)
SELECT department, COUNT(*) as employee_count, AVG(salary) as avg_salary
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.hire_date > '2020-01-01'
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY avg_salary DESC
LIMIT 10;

-- 逻辑执行顺序(数据库解析)
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.hire_date > '2020-01-01'
GROUP BY department
HAVING COUNT(*) > 5
SELECT department, COUNT(*) as employee_count, AVG(salary) as avg_salary
ORDER BY avg_salary DESC
LIMIT 10; 

WHERE子句中不能使用SELECT的列别名
ORDER BY子句中可以使用SELECT的列别名

数据更新

一、插入数据 (INSERT)

INSERT 语句用于向数据库表中添加新的数据行。

1. 语句格式
sql 复制代码
-- 插入完整行(需按表结构顺序提供所有值)
INSERT INTO table_name VALUES (value1, value2, ...);

-- 插入指定字段(未指定的字段使用默认值或NULL)
INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...);

-- 插入多行记录
INSERT INTO table_name (column1, column2, ...) 
VALUES 
    (value1_1, value1_2, ...),
    (value2_1, value2_2, ...),
    ...;

-- 使用SET形式插入记录
INSERT INTO table_name SET column1 = value1, column2 = value2, ...;

-- 将查询结果插入到表中
INSERT INTO target_table (column1, column2, ...)
SELECT column1, column2, ... FROM source_table [WHERE condition];
2. 使用说明和注意事项
  • 如果某列有默认值或允许NULL,可在INSERT语句中省略该列
  • 字符型和日期型数据需用单引号 (') 括起,数值型可直接书写
  • 从其他表插入时,SELECT子句的列结构与目标表需兼容
  • 使用INSERT INTO ... SELECT可以进行大容量数据加载,按最小方式记录日志以提高性能
3. 示例
sql 复制代码
-- 向employees表插入一条完整记录
INSERT INTO employees VALUES (1, '张三', '销售部', 5000, '2023-01-15');

-- 只插入姓名和部门,其他字段使用默认值
INSERT INTO employees (emp_name, department) VALUES ('李四', '技术部');

-- 一次性插入多条记录
INSERT INTO employees (emp_name, department, salary) 
VALUES 
    ('王五', '技术部', 6000),
    ('赵六', '市场部', 5500),
    ('钱七', '人事部', 5200);

-- 从临时表插入数据
INSERT INTO permanent_employees (emp_id, emp_name, department)
SELECT id, name, dept FROM temp_employees WHERE hire_date > '2023-01-01';

二、 更新数据 (UPDATE)

UPDATE语句用于修改表中已存在的数据。

1. 语句格式
sql 复制代码
UPDATE table_name 
SET column1 = new_value1, column2 = new_value2, ...
[WHERE condition]
[ORDER BY column_name]
[LIMIT row_count];
2. 使用说明和注意事项
  • WHERE子句是更新的安全锁 ,明确指定要更新哪些行。务必谨慎,省略WHERE子句将更新表中所有行
  • SET子句可以包含表达式,如salary = salary * 1.1
  • 可以同时更新多个列,用逗号分隔
  • 要删除某列的值,可以设置为NULL
3. 示例
sql 复制代码
-- 将员工ID为1的工资改为6000
UPDATE employees SET salary = 6000 WHERE emp_id = 1;

-- 将技术部所有员工工资上涨10%
UPDATE employees SET salary = salary * 1.1 WHERE department = '技术部';

-- 同时更新邮箱和电话号码
UPDATE employees 
SET email = 'lisi@company.com', phone = '13800138000' 
WHERE emp_name = '李四';

-- 将邮箱设置为NULL(清空邮箱)
UPDATE employees SET email = NULL WHERE emp_id = 3;

-- 按工龄排序,只更新前5名员工的工资
UPDATE employees SET salary = salary * 1.2 
ORDER BY hire_date ASC LIMIT 5;

三、 删除数据 (DELETE)

DELETE语句用于从表中删除数据行。

1. 语句格式
sql 复制代码
-- 单表删除
DELETE FROM table_name [WHERE condition] [ORDER BY column_name] [LIMIT row_count];

-- 多表删除(部分数据库支持)
DELETE table_name1
FROM table_name1 [INNER JOIN | LEFT JOIN | RIGHT JOIN] table_name2 
ON join_condition
[WHERE where_condition];
2. 使用说明和注意事项
  • WHERE子句是删除操作的安全锁 ,明确指定要删除哪些行。务必谨慎,省略WHERE子句将删除表中所有数据
  • DELETE操作只删除数据,不删除表结构
  • 如需快速清空整个表且不可回滚,TRUNCATE TABLE语句通常性能更好
  • 执行DELETE命令时,表中必须存在主键(某些数据库要求)
3. 示例
sql 复制代码
-- 删除指定员工记录
DELETE FROM employees WHERE emp_id = 5;

-- 删除所有工资低于4000的员工记录
DELETE FROM employees WHERE salary < 4000;

-- 删除销售部门的所有员工
DELETE FROM employees WHERE department = '销售部';

-- 按工龄排序,删除最早入职的3名员工
DELETE FROM employees ORDER BY hire_date ASC LIMIT 3;

-- 多表删除:删除在离职员工表中也有记录的员工
DELETE employees
FROM employees 
INNER JOIN resigned_employees ON employees.emp_id = resigned_employees.emp_id;

四、 清空表数据 (TRUNCATE)

TRUNCATE用于快速清空整个表的数据。

1. 语句格式
sql 复制代码
TRUNCATE [TABLE] table_name;
2. 使用说明和注意事项
  • 比DELETE删除所有数据更快,因为它不记录单独的删除操作
  • 不能使用WHERE条件,总是删除整个表的数据
  • 重置自增计数器(如AUTO_INCREMENT)
  • 通常不可回滚(取决于数据库系统)
3. 示例
sql 复制代码
-- 清空临时表的所有数据
TRUNCATE TABLE temp_employees;

-- 清空日志表(语法可能因数据库而异)
TRUNCATE audit_log;

五、空值

SQL 中的空值(NULL)是表示"未知"或"缺失"的特殊标记。理解并正确处理 NULL 是编写可靠 SQL 查询的关键。下面这个表格汇总了空值的核心概念与约束。

特性 说明/规则
定义 表示"未知"、"缺失"或"不适用"的值,与数字 0 或空字符串 '' 完全不同 。
判断方式 必须使用 IS NULLIS NOT NULL。使用 =<> 等比较运算符与 NULL 比较的结果是 UNKNOWN,而非 TRUEFALSE
唯一约束 (UNIQUE) 允许多个 NULL 值存在(通常被视为不同的值)。例外 :在 SQL Server 中,唯一约束只允许一个 NULL 值 。
非空约束 (NOT NULL) 禁止该列存在 NULL 值 。
主键约束 (PRIMARY KEY) 完全不允许 NULL 值,相当于 NOT NULLUNIQUE 的结合 。
外键约束 (FOREIGN KEY) 外键列本身允许NULL,表示与父表的关联是可选的
1. 空值在各种运算中的行为

空值的"未知"本质决定了它在运算中的特殊规则。

1.1 算术运算

任何与 NULL 进行的算术运算(+, -, *, /),结果都是 NULL

sql 复制代码
SELECT 10 + NULL;   -- 结果为 NULL
SELECT 5 * NULL;     -- 结果为 NULL
SELECT NULL / 2;     -- 结果为 NULL
1.2 比较运算与三值逻辑

在 SQL 中,NULL 与任何值(包括另一个 NULL)进行常规比较,结果都不是 TRUEFALSE,而是第三个值:UNKNOWNWHEREHAVINGON 等子句只返回条件结果为 TRUE 的行 ,会过滤掉结果为 FALSEUNKNOWN 的行 。

  • 正确判断空值 :必须使用专用的操作符 IS NULLIS NOT NULL

    sql 复制代码
    -- 错误:无法正确筛选出 manager_id 为 NULL 的记录
    SELECT * FROM employees WHERE manager_id = NULL; -- 结果永远为空
    
    -- 正确:使用 IS NULL
    SELECT * FROM employees WHERE manager_id IS NULL;
  • 三值逻辑(3VL)UNKNOWN 参与 ANDORNOT 运算时,结果可能反直觉 。下表展示了其运算规则:

AND TRUE FALSE UNKNOWN
TRUE TRUE FALSE UNKNOWN
FALSE FALSE FALSE FALSE
UNKNOWN UNKNOWN FALSE UNKNOWN
OR TRUE FALSE UNKNOWN
TRUE TRUE TRUE TRUE
FALSE TRUE FALSE UNKNOWN
UNKNOWN TRUE UNKNOWN UNKNOWN
操作数 NOT
TRUE FALSE
FALSE TRUE
UNKNOWN UNKNOWN
1.3 聚合函数

聚合函数(如 COUNT(), SUM(), AVG() 等)会自动忽略 NULL ,但 COUNT(*) 除外(统计所有行数)。

sql 复制代码
-- 假设 commission_pct 列有 NULL 值
SELECT COUNT(commission_pct) FROM employees; -- 只统计非 NULL 值的数量
SELECT AVG(commission_pct) FROM employees;   -- 只对非 NULL 值计算平均值
1.4 分组与排序
  • GROUP BY :所有的 NULL 会被视为相同的值,分到同一组 。
  • ORDER BYNULL 的排序位置因数据库而异。可以使用 ORDER BY ... NULLS FIRST/LAST 来显式控制(如果数据库支持)。
2. 常见陷阱与处理函数
2.1 NOT IN 子查询中的 NULL

如果 NOT IN 子查询返回的结果集中包含 NULL 值,整个查询可能不会返回任何结果 。这是因为 NOT IN 等价于一系列 <> 比较,任何值与 UNKNOWN 比较的结果仍是 UNKNOWN

sql 复制代码
-- 危险:如果子查询结果含NULL,可能无结果
SELECT * FROM TableA WHERE id NOT IN (SELECT maybe_null_id FROM TableB);

-- 安全做法:使用 NOT EXISTS
SELECT * FROM TableA a WHERE NOT EXISTS (SELECT 1 FROM TableB b WHERE b.maybe_null_id = a.id);
2.2 空值处理函数
  • COALESCE(val1, val2, ...) :返回参数列表中第一个非 NULL 的值 。常用于将 NULL 替换为默认值 。

    sql 复制代码
    SELECT COALESCE(Address, '未知地址') FROM Users; -- 如果 Address 为 NULL,则返回 '未知地址'
  • NULLIF(expr1, expr2) :如果 expr1 等于 expr2,则返回 NULL;否则返回 expr1。常用于防止除零错误 。

    sql 复制代码
    SELECT 1 / NULLIF(Column, 0) FROM table; -- 如果 Column 为 0,则 NULLIF 返回 NULL,最终结果为 NULL,避免报错
  • 数据库特有函数:MySQL 有 IFNULL(expr1, expr2),SQL Server 有 ISNULL(expr1, expr2),它们都相当于两个参数的 COALESCE

视图

一、 视图的定义

视图是一个虚拟表 ,其内容由一条 SELECT查询语句定义。它本身不存储数据,而是从基表(原始表)中动态生成结果集。

自始至终只存在视图定义,不执行SELECT语句实际组成表格;视图查询时,按定义从基本表中取出。

核心要点

  • 作用:简化复杂查询、提高数据安全性(只暴露特定数据)、提供逻辑数据独立性。
  • 优点:视点集中、简化操作、定制数据、增强安全性。
  • 缺点:复杂视图可能有效能开销,更新操作受限。

二、 视图的基本操作

1. 创建视图

使用 CREATE VIEW语句。

sql 复制代码
-- 基本语法
CREATE VIEW view_name [(column_name_list)]
AS <子查询>
[WITH CHECK OPTION]; -- 可选,用于限制更新操作

子查询不允许含有ORDER BY子句和DISTINCT短语。
WITH CHECK OPTION 限制更新子查询中where中条件。

示例1:创建单表视图

sql 复制代码
-- 创建一个显示薪资高于50000的员工的视图
CREATE VIEW high_earners AS
SELECT employee_id, name, salary
FROM employees
WHERE salary > 50000;

示例2:创建多表连接视图

sql 复制代码
-- 创建一个显示员工及其部门信息的视图
CREATE VIEW emp_dept_info AS
SELECT e.emp_id, e.emp_name, d.dept_name, e.salary
FROM employee e
JOIN department d ON e.dept_id = d.dept_id;

CREATE VIEW emp_dept_info AS
SELECT *
FROM employee e
JOIN department d ON e.dept_id = d.dept_id;

示例3:创建含聚合函数的视图

sql 复制代码
-- 创建一个显示各部门薪资总和的视图
CREATE VIEW department_salary_sum AS
SELECT department_id, SUM(salary) AS total_salary
FROM employees
GROUP BY department_id;
2. 查询视图

查询视图与查询普通表语法完全一致。

sql 复制代码
-- 查询整个视图
SELECT * FROM high_earners;

-- 带条件查询视图
SELECT emp_name, dept_name FROM emp_dept_info WHERE salary > 7000;
3. 修改视图

使用 CREATE OR REPLACE VIEWALTER VIEW

sql 复制代码
-- 方法1:使用 OR REPLACE(如果视图不存在则创建,存在则替换)
CREATE OR REPLACE VIEW high_earners AS
SELECT employee_id, name, salary, hire_date
FROM employees
WHERE salary > 60000; 

-- 方法2:使用 ALTER VIEW
ALTER VIEW high_earners AS
SELECT employee_id, name, salary
FROM employees
WHERE salary > 55000;
4. 删除视图

使用 DROP VIEW语句。

sql 复制代码
-- 删除视图
DROP VIEW high_earners;

-- 安全删除(如果视图存在才删除,避免错误)
DROP VIEW IF EXISTS high_earners;

删除基表的时候不会自动删除视图的定义,但会删除视图的数据。

三、 视图的受限更新

通过视图更新数据(INSERT, UPDATE, DELETE)会直接作用到基表 ,但并非所有视图都支持更新。以下情况通常不可更新

限制情况 示例
包含聚合函数 (如 SUM, AVG, COUNT CREATE VIEW DeptStats AS SELECT DeptID, COUNT(*) EmpCount FROM Employees GROUP BY DeptID;
包含 DISTINCT, GROUP BY, HAVING CREATE VIEW HighSalaries AS SELECT DISTINCT Salary FROM Employees WHERE Salary > 5000;
基于多表连接 (JOIN) CREATE VIEW EmpDept AS SELECT e.Name, d.DeptName FROM Employees e JOIN Departments d ON e.DeptID = d.DeptID;
包含子查询或联合查询 (UNION) CREATE VIEW SeniorEmps AS SELECT * FROM Employees WHERE Salary > (SELECT AVG(Salary) FROM Employees);
不包含基表的所有非空列 (对于 INSERT 如果基表有非空列未包含在视图中,则无法通过视图插入数据。

WITH CHECK OPTION子句

在创建视图时使用 WITH CHECK OPTION,可以确保通过视图进行的更新和插入操作符合视图定义的筛选条件

sql 复制代码
-- 创建一个只允许查看和更新女员工信息的视图
CREATE VIEW female_employees AS
SELECT employee_id, name, gender, department
FROM employees
WHERE gender = 'F'
WITH CHECK OPTION; -- 确保更新/插入的数据满足 gender='F'

-- 尝试更新为男性将失败(因为违反了视图的WHERE条件)
UPDATE female_employees SET gender = 'M' WHERE employee_id = 101;

四、 定义基于视图的新视图(视图嵌套)

可以基于一个或多个已存在的视图来定义新的视图。

sql 复制代码
-- 假设已有视图 v_employees 和 v_departments
CREATE VIEW v_employees AS SELECT emp_id, emp_name, dept_id FROM employees;
CREATE VIEW v_departments AS SELECT dept_id, dept_name FROM departments;

-- 基于上述视图创建新的嵌套视图
CREATE VIEW v_emp_dept AS
SELECT e.emp_name, d.dept_name
FROM v_employees e
JOIN v_departments d ON e.dept_id = d.dept_id;

注意事项

  • 过度嵌套可能导致查询性能下降,因为数据库需要逐层解析。
  • 如果底层视图被修改或删除,依赖于它的上层视图可能会失效。
sql 复制代码
-- 删除视图
DROP VIEW high_earners;

-- 安全删除(如果视图存在才删除,避免错误)
DROP VIEW IF EXISTS high_earners;

删除基表的时候不会自动删除视图的定义,但会删除视图的数据。

三、 视图的受限更新

通过视图更新数据(INSERT, UPDATE, DELETE)会直接作用到基表 ,但并非所有视图都支持更新。以下情况通常不可更新

限制情况 示例
包含聚合函数 (如 SUM, AVG, COUNT CREATE VIEW DeptStats AS SELECT DeptID, COUNT(*) EmpCount FROM Employees GROUP BY DeptID;
包含 DISTINCT, GROUP BY, HAVING CREATE VIEW HighSalaries AS SELECT DISTINCT Salary FROM Employees WHERE Salary > 5000;
基于多表连接 (JOIN) CREATE VIEW EmpDept AS SELECT e.Name, d.DeptName FROM Employees e JOIN Departments d ON e.DeptID = d.DeptID;
包含子查询或联合查询 (UNION) CREATE VIEW SeniorEmps AS SELECT * FROM Employees WHERE Salary > (SELECT AVG(Salary) FROM Employees);
不包含基表的所有非空列 (对于 INSERT 如果基表有非空列未包含在视图中,则无法通过视图插入数据。

WITH CHECK OPTION子句

在创建视图时使用 WITH CHECK OPTION,可以确保通过视图进行的更新和插入操作符合视图定义的筛选条件

sql 复制代码
-- 创建一个只允许查看和更新女员工信息的视图
CREATE VIEW female_employees AS
SELECT employee_id, name, gender, department
FROM employees
WHERE gender = 'F'
WITH CHECK OPTION; -- 确保更新/插入的数据满足 gender='F'

-- 尝试更新为男性将失败(因为违反了视图的WHERE条件)
UPDATE female_employees SET gender = 'M' WHERE employee_id = 101;

四、 定义基于视图的新视图(视图嵌套)

可以基于一个或多个已存在的视图来定义新的视图。

sql 复制代码
-- 假设已有视图 v_employees 和 v_departments
CREATE VIEW v_employees AS SELECT emp_id, emp_name, dept_id FROM employees;
CREATE VIEW v_departments AS SELECT dept_id, dept_name FROM departments;

-- 基于上述视图创建新的嵌套视图
CREATE VIEW v_emp_dept AS
SELECT e.emp_name, d.dept_name
FROM v_employees e
JOIN v_departments d ON e.dept_id = d.dept_id;

注意事项

  • 过度嵌套可能导致查询性能下降,因为数据库需要逐层解析。
  • 如果底层视图被修改或删除,依赖于它的上层视图可能会失效。
相关推荐
陌上丨2 小时前
Redis内存使用率在95%以上,请问是什么原因?如何解决?
数据库·redis·缓存
m0_561359672 小时前
使用PyQt5创建现代化的桌面应用程序
jvm·数据库·python
2301_790300962 小时前
用Python实现自动化的Web测试(Selenium)
jvm·数据库·python
m0_561359672 小时前
使用Docker容器化你的Python应用
jvm·数据库·python
一条闲鱼_mytube2 小时前
MySQL vs PostgreSQL 对比
数据库·mysql·postgresql
Maynor9962 小时前
Clawdbot安装教程:从零开始到接入飞书
java·数据库·飞书
小北方城市网2 小时前
Spring Boot 多数据源与事务管理实战:主从分离、动态切换与事务一致性
java·开发语言·jvm·数据库·mysql·oracle·mybatis
u0109272713 小时前
使用Scrapy框架构建分布式爬虫
jvm·数据库·python
l1t3 小时前
DeekSeek辅助总结PostgreSQL Mistakes and How to Avoid Them 的一个例子
数据库·postgresql