复杂一点的sql查询

第一模块:视图 ------ 虚拟的"窗口"

1. 概念与区别 大家要记住一句话:视图是虚拟的表,它本身不存储数据,只存储 SQL 查询逻辑。

  • :实际存储数据的地方(硬盘上)。
  • 视图:就像一个窗口,透过窗口看真实的数据。窗口关闭(删除视图),数据还在。

2. 为什么需要视图?

  • 复用性:把频繁使用的复杂查询存起来,下次直接用,不用重复写。
  • 安全性:只开放视图权限,不开放原表权限,用户只能看到视图里展示的字段。
  • 清晰度:把复杂的逻辑封装起来,对外看起来就像操作一张简单的表。

3. 核心语法

  • 创建CREATE VIEW 视图名 (列名...) AS <SELECT语句>
  • 修改ALTER VIEW 视图名 AS <SELECT语句>
  • 更新/删除 :虽然可以对视图进行 UPDATEINSERT,但这会直接影响原表数据,且有很多限制(比如包含 GROUP BY 的视图就不能更新)。工作中通常不建议直接更新视图,而是更新原表。

1.3 视图的核心操作(创建 / 修改 / 更新 / 删除 / 查看)

(1)创建视图【核心】

语法:

sql

cpp 复制代码
CREATE VIEW <视图名>(列名1,列名2,...) AS <SELECT语句>;
  • 视图名在数据库中唯一,不能和表 / 其他视图重名;
  • SELECT 语句可以是单表查询、联表查询、带聚合的查询
  • 列名可省略,默认使用 SELECT 语句的列名。

示例 1:单表视图(按商品类型统计数量)

台湾 sql

cpp 复制代码
CREATE VIEW productsum (product_type, cnt_product)
AS
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type;

示例 2:多表视图(联表 product 和 shop_product,查商品类型、售价、商店名)

cpp 复制代码
CREATE VIEW view_shop_product(product_type, sale_price, shop_name)
AS
SELECT product_type, sale_price, shop_name
FROM product
JOIN shop_product
WHERE product.product_id = shop_product.product_id;

创建一个叫 view_shop_product 的视图(虚拟表),把「商品表(product)」和「店铺商品库存表(shop_product)」按「商品 ID」拼在一起,最终视图里只展示「商品类型、商品售价、店铺名称」这三列数据

(2)修改视图
cpp 复制代码
ALTER VIEW <视图名> AS <新的SELECT语句>;

本质是替换视图的查询逻辑,示例:

cpp 复制代码
ALTER VIEW productsum
AS
SELECT product_type, sale_price
FROM product
WHERE regist_date > '2009-09-11';
(3)更新视图内容

视图是虚拟表,更新视图就是更新底层的基础表,语法和更新普通表一致(UPDATE/INSERT/DELETE),但有前提:

  • 视图的 SELECT 语句不能包含 GROUP BY、DISTINCT、聚合函数(否则无法更新);
  • 只能更新视图 "窗口能看到的内容",底层表中未被视图包含的字段无法通过视图更新。

示例:更新 productsum 视图中办公用品的售价

sql

cpp 复制代码
UPDATE productsum
SET sale_price = '5000'
WHERE product_type = '办公用品';

⚠️ 重要提醒 :实际工作中强烈不推荐更新视图!直接操作视图容易导致底层表数据误改,规范做法是直接操作底层表。

(4)删除视图

语法:

sql

cpp 复制代码
DROP VIEW <视图名1>[,视图名2...];

示例:

sql

cpp 复制代码
DROP VIEW productsum;
(5)查看视图

两种常用方式,推荐第二种(更通用):

sql

cpp 复制代码
-- 方式1:查指定库下的所有视图
USE shop; -- 切换到视图所在库
SHOW TABLE STATUS WHERE COMMENT = 'view';

-- 方式2:从系统表中查询(推荐)
SELECT * FROM information_schema.views
WHERE TABLE_SCHEMA = 'shop'; -- shop是数据库名

1.4 视图的使用禁忌

  1. 避免多层视图:不要在视图的基础上再创建视图,会大幅降低 SQL 执行效率;
  2. 不要用视图做数据更新 :仅用视图做查询,更新操作直接针对底层表;
  3. 视图的 SELECT 语句要简洁:过于复杂的联表 / 聚合会导致视图查询速度变慢。

二、子查询:一次性的 "临时视图"

子查询就是将 SELECT 语句嵌套在另一个 SQL 语句中 ,它和视图的关系非常紧密:子查询是一次性的视图,不存储在数据库中,SQL 执行后就消失

2.1 子查询与视图的关联

  • 视图:将 SELECT 语句永久保存,可反复使用;
  • 子查询:将 SELECT 语句临时嵌套,仅本次 SQL 使用,执行后销毁。

比如创建视图的语句CREATE VIEW v AS SELECT ...,把这个SELECT ...直接嵌套到其他 SQL 中,就是子查询。

2.2 子查询的分类与用法

根据使用场景和返回结果,子查询分为嵌套子查询、标量子查询、关联子查询 ,其中标量子查询和关联子查询是实际开发的重点

(1)嵌套子查询

将子查询嵌套在另一个子查询中,语法上无限制,但层数越多,SQL 越难理解、执行效率越低尽量避免

示例:查询商品类型数量为 4 的商品类型和数量

sql

cpp 复制代码
SELECT product_type, cnt_product
FROM (SELECT *
      FROM (SELECT product_type, COUNT(*) AS cnt_product
            FROM product 
            GROUP BY product_type) AS productsum
      WHERE cnt_product = 4) AS productsum2;

这个示例用了两层嵌套,实际可以直接用GROUP BY + HAVING实现,无需嵌套子查询,这也是为什么不推荐嵌套的原因。

(2)标量子查询【核心】

标量 = 单一 ,标量子查询的要求是:返回结果必须是「单一值」(一行一列),比如返回一个数字、一个字符串、一个日期。

核心优势

标量子查询的使用场景无限制任何可以使用常数 / 列名的地方都能使用(SELECT、WHERE、GROUP BY、HAVING、ORDER BY 子句均可),这是它最强大的地方。

经典示例

示例 1:WHERE 子句中使用(查询售价高于平均售价的商品)

sql

sql 复制代码
SELECT product_id, product_name, sale_price
FROM product
WHERE sale_price > (SELECT AVG(sale_price) FROM product);

内层的SELECT AVG(sale_price) FROM product就是标量子查询,返回一个单一的平均售价。

示例 2:SELECT 子句中使用(查询每个商品的售价 + 全表平均售价)

sql

sql 复制代码
SELECT product_id,
       product_name,
       sale_price,
       (SELECT AVG(sale_price) FROM product) AS avg_price
FROM product;

此时平均售价会作为一列,展示在每个商品的右侧,这是基础查询无法实现的。

⚠️ 易错点

标量子查询必须返回且仅返回一个值,如果返回 0 行或多行,都会报语法错误。

(3)关联子查询【核心】

关联子查询是外层查询和内层子查询之间存在关联条件 的子查询,核心解决 **「按分组查条件」** 的需求,比如:查询各商品类型中,售价高于该类型平均售价的商品(基础查询 / 标量子查询无法实现)。

经典示例(按类型查高于该类型均价的商品)

sql

sql 复制代码
SELECT product_type, product_name, sale_price
FROM product AS p1  -- 外层表起别名p1
WHERE sale_price > (SELECT AVG(sale_price)
                    FROM product AS p2  -- 内层表起别名p2
                    WHERE p1.product_type = p2.product_type  -- 关联条件:内外层类型一致
                    GROUP BY product_type);
执行逻辑(关键理解)
  1. 先执行外层查询,取出 p1 的第一条记录(比如 T 恤,衣服类型);
  2. 根据关联条件p1.product_type = p2.product_type,内层子查询只计算衣服类型的平均售价;
  3. 判断外层记录的售价是否高于该平均售价,符合则保留;
  4. 依次遍历外层表的所有记录,重复上述步骤。
核心要点
  • 必须给内外层表起不同的别名(如 p1、p2),否则数据库无法区分;
  • 关联条件是核心,必须将内外层表的某个字段关联(通常是分组字段)。

三、SQL 内置函数:提升数据处理效率

SQL 提供了大量内置函数,用于快速实现数值计算、字符串处理、日期操作、数据类型转换 ,无需自己写复杂逻辑。函数总数超 200 个,不用死记,掌握 30-50 个常用的即可,其余可按需查阅文档。

函数的通用特点:输入参数→按预设逻辑计算→返回单一值 ,我们按功能分类讲解最常用的函数

3.1 算术函数:数值计算

针对数值类型的字段做计算,常用的有 3 个:

函数 语法 作用 示例 结果
ABS ABS (数值) 求绝对值 ABS(-180) 180
MOD MOD (被除数,除数) 求余数(仅整数) MOD(7,3) 1
ROUND ROUND (数值,保留小数位) 四舍五入 ROUND(5.555,1) 5.6

3.2 字符串函数:字符串操作

针对字符串类型的字段,实际开发中高频使用,常用的有:

函数 语法 作用
CONCAT CONCAT (字符串 1, 字符串 2,...) 拼接多个字符串
LENGTH LENGTH (字符串) 求字符串长度(字节数)
LOWER/UPPER LOWER (字符串)/UPPER (字符串) 大小写转换(仅英文)
REPLACE REPLACE (原字符串,旧值,新值) 替换字符串中的指定内容
SUBSTRING SUBSTRING (字符串 FROM 起始位 FOR 长度) 截取字符串(起始位从 1 开始)
SUBSTRING_INDEX SUBSTRING_INDEX (原字符串,分隔符,n) 按分隔符截取(n 正取前 n 个,n 负取后 n 个)

经典示例

sql

sql 复制代码
-- 拼接:太阳+月亮+火星 → 太阳月亮火星
SELECT CONCAT('太阳','月亮','火星');
-- 截取:从第3位开始取2个字符,abcdef → cd
SELECT SUBSTRING('abcdef' FROM 3 FOR 2);
-- 按分隔符截取:www.mysql.com 按.取前2个 → www.mysql
SELECT SUBSTRING_INDEX('www.mysql.com', '.', 2);

3.3 日期函数:日期 / 时间操作

日期处理是开发中的高频需求,SQL 的日期函数能快速提取年 / 月 / 日、获取当前时间,常用的有:

函数 语法 作用
CURRENT_DATE CURRENT_DATE 获取当前日期(YYYY-MM-DD)
CURRENT_TIME CURRENT_TIME 获取当前时间(HH:MM:SS)
CURRENT_TIMESTAMP CURRENT_TIMESTAMP 获取当前日期 + 时间
EXTRACT EXTRACT (日期元素 FROM 日期) 提取日期元素(年 / 月 / 日 / 时 / 分 / 秒)

经典示例

sql

sql 复制代码
-- 获取当前日期
SELECT CURRENT_DATE; -- 结果:2026-02-23
-- 提取年/月/日
SELECT EXTRACT(YEAR FROM '2009-09-20') AS year,
       EXTRACT(MONTH FROM '2009-09-20') AS month,
       EXTRACT(DAY FROM '2009-09-20') AS day;

3.4 转换函数:类型 / 值的转换

核心解决数据类型转换NULL 值处理 问题,COALESCE 是处理 NULL 的核心函数,必须掌握:

(1)CAST:数据类型转换

语法:

sql

sql 复制代码
CAST(转换前的值 AS 目标数据类型)

常用转换:字符串→数值、字符串→日期、数值→字符串

sql

sql 复制代码
-- 字符串→整数
SELECT CAST('0001' AS SIGNED INTEGER); -- 结果:1
-- 字符串→日期
SELECT CAST('2009-09-20' AS DATE); -- 结果:2009-09-20
(2)COALESCE:将 NULL 转换为指定值

语法:

sql

sql 复制代码
COALESCE(值1,值2,值3,...)

作用 :返回参数中从左到右第一个非 NULL 的值 ,参数个数无限制,是处理 NULL 的最优方案(避免 NULL 参与计算导致结果为 NULL)。

示例:

sql

sql 复制代码
-- 第一个参数是NULL,返回第二个11
SELECT COALESCE(NULL,11); -- 11
-- 前两个是NULL,返回第三个2026-02-23
SELECT COALESCE(NULL,NULL,'2026-02-23'); -- 2026-02-23
-- 处理字段的NULL:如果purchase_price为NULL,返回0
SELECT product_name, COALESCE(purchase_price,0) FROM product;

四、谓词:返回 "真值" 的特殊函数

谓词是 SQL 中专门用于判断条件 的函数,核心特点:返回值为「真值」(TRUE / 假 FALSE / 未知 UNKNOWN),而非普通的数值 / 字符串。

常用的谓词有:LIKEBETWEENIS NULL/IS NOT NULLIN/NOT INEXIST/NOT EXIST,这些是WHERE 子句的核心组成,必须掌握。

4.1 LIKE:模糊查询

用于字符串的部分匹配查询,搭配两个通配符使用,核心解决 "不知道完整字符串,只知道部分内容" 的查询需求:

  • %:匹配0 个或多个任意字符
  • _:匹配1 个任意字符(精确匹配 1 个,不能多也不能少)。
三种匹配方式

sql

sql 复制代码
-- 前方一致:以ddd开头的字符串
SELECT * FROM samplelike WHERE strcol LIKE 'ddd%';
-- 中间一致:包含ddd的字符串(最宽松,结果最多)
SELECT * FROM samplelike WHERE strcol LIKE '%ddd%';
-- 后方一致:以ddd结尾的字符串
SELECT * FROM samplelike WHERE strcol LIKE '%ddd';
-- 匹配任意1个字符:abc后接2个任意字符 → abcdd
SELECT * FROM samplelike WHERE strcol LIKE 'abc__';

4.2 BETWEEN:范围查询

用于数值 / 日期的范围查询,语法:

sql

sql 复制代码
字段 BETWEEN 最小值 AND 最大值

⚠️ 关键BETWEEN闭区间 ,包含最小值和最大值 ,如果不想包含临界值,用< >代替。

示例:

sql

sql 复制代码
-- 查售价100~1000的商品(包含100和1000)
SELECT product_name, sale_price FROM product WHERE sale_price BETWEEN 100 AND 1000;
-- 查售价100~1000的商品(不包含100和1000)
SELECT product_name, sale_price FROM product WHERE sale_price >100 AND sale_price <1000;

4.3 IS NULL/IS NOT NULL:判断 NULL

唯一能判断 NULL 值的谓词永远不要用=/!=判断 NULL(因为 NULL 是 "未知值",和任何值比较结果都是 UNKNOWN)。

示例:

sql

sql 复制代码
-- 查进价为NULL的商品
SELECT product_name, purchase_price FROM product WHERE purchase_price IS NULL;
-- 查进价不为NULL的商品
SELECT product_name, purchase_price FROM product WHERE purchase_price IS NOT NULL;

4.4 IN/NOT IN:替代 OR 的批量查询

用于多个值的批量匹配 ,替代多个OR,让 SQL 更简洁,语法:

sql

复制代码
字段 IN (值1,值2,值3,...); -- 匹配任意一个值
字段 NOT IN (值1,值2,值3,...); -- 不匹配任何一个值
基础用法(替代 OR)

sql

sql 复制代码
-- 用OR:查进价为320/500/5000的商品
SELECT product_name, purchase_price FROM product WHERE purchase_price=320 OR purchase_price=500 OR purchase_price=5000;
-- 用IN:更简洁
SELECT product_name, purchase_price FROM product WHERE purchase_price IN (320,500,5000);
高级用法:子查询作为 IN 的参数【核心】

IN 的参数不仅可以是常量,还可以是子查询的结果,这是实际开发的高频场景,解决 **"动态批量匹配"** 问题(比如查大阪门店在售的商品)。

示例:查大阪门店(shop_id='000C')在售的商品名称和售价

sql

sql 复制代码
SELECT product_name, sale_price
FROM product
WHERE product_id IN (SELECT product_id  -- 子查询:查大阪门店的商品ID
                     FROM shop_product
                     WHERE shop_id = '000C');

优势 :当大阪门店的在售商品变化时,无需修改外层 SQL,子查询会自动获取最新的商品 ID,可维护性大幅提升

⚠️ 易错点

IN/NOT IN的参数中不能包含 NULL,否则会导致查询结果异常(查不到任何数据)。

4.5 EXIST/NOT EXIST:判断 "记录是否存在"

EXIST 的核心作用:判断子查询中是否存在满足条件的记录,存在则返回 TRUE,不存在则返回 FALSE。

  • EXIST:存在→TRUE;
  • NOT EXIST:不存在→TRUE。
基础用法(替代 IN)

示例:用 EXIST 实现 "查大阪门店在售的商品",和 IN 的结果一致

sql

sql 复制代码
SELECT product_name, sale_price
FROM product AS p
WHERE EXISTS (SELECT *  -- 子查询:关联商品ID和门店ID
              FROM shop_product AS sp
              WHERE sp.shop_id = '000C'
              AND sp.product_id = p.product_id);
核心特点
  1. EXIST只有一个参数 :通常是关联子查询(内外层表有联表条件);
  2. 子查询中写SELECT */SELECT 1都可以,因为 EXIST只关心记录是否存在,不关心返回什么列
  3. 大数据量下,EXIST 的执行效率远高于 IN(因为 EXIST 找到第一条满足条件的记录就会停止查询,而 IN 会查询所有结果)。
NOT EXIST:替代 NOT IN

示例:查未在东京门店(shop_id='000A')销售的商品

sql

sql 复制代码
SELECT product_name, sale_price
FROM product AS p
WHERE NOT EXISTS (SELECT *
                  FROM shop_product AS sp
                  WHERE sp.shop_id = '000A'
                  AND sp.product_id = p.product_id);

五、CASE 表达式:SQL 的 "条件分支"【重中之重】

CASE 表达式是 SQL 中最强大、最常用 的功能之一,没有之一!它是 SQL 中的条件分支语句 ,能实现 "如果... 那么... 否则..." 的逻辑,解决数据分类、格式转换、行列转换等核心需求。

5.1 核心语法(搜索型 CASE,推荐)

SQL 中有简单型和搜索型 CASE,搜索型包含简单型的所有功能,只需掌握搜索型即可:

sql

sql 复制代码
CASE
    WHEN <求值表达式> THEN <返回值>
    WHEN <求值表达式> THEN <返回值>
    ...
    ELSE <返回值>  -- 可选,默认NULL
END  -- 必须写,不能省略!

执行逻辑 :依次判断WHEN后的表达式是否为 TRUE,第一个为 TRUE 的WHEN返回对应THEN的值;如果所有WHEN都为 FALSE,返回ELSE的值(无 ELSE 则返回 NULL)。

5.2 核心特点

  1. 返回单一值:无论多少个 WHEN 分支,最终只会返回一个值;
  2. END 不能省:忘记写 END 会报语法错误,这是新手最常见的错误;
  3. ELSE 可选:但推荐显式写出,避免 NULL 值导致的结果异常;
  4. 使用场景无限制:可在 SELECT、WHERE、GROUP BY、HAVING、ORDER BY 中使用。

5.3 三大核心应用场景【实际开发高频】

场景 1:列值替换 / 分类(最基础)

将原有列值按条件转换为新的值,比如给商品类型加前缀(衣服→A:衣服,办公用品→B:办公用品)。

示例:

sql

sql 复制代码
SELECT product_name,
       CASE WHEN product_type = '衣服' THEN CONCAT('A : ',product_type)
            WHEN product_type = '办公用品' THEN CONCAT('B : ',product_type)
            WHEN product_type = '厨房用具' THEN CONCAT('C : ',product_type)
            ELSE NULL
       END AS abc_product_type
FROM product;
场景 2:列方向的聚合统计(核心)

基础的GROUP BY行方向 的统计(按分组显示一行结果),而 CASE 表达式可以实现列方向的统计(按分组显示一列结果),解决 "多维度一行展示" 的需求。

示例:统计衣服、厨房用具、办公用品的售价总和,一行三列展示

sql

sql 复制代码
SELECT SUM(CASE WHEN product_type = '衣服' THEN sale_price ELSE 0 END) AS sum_price_clothes,
       SUM(CASE WHEN product_type = '厨房用具' THEN sale_price ELSE 0 END) AS sum_price_kitchen,
       SUM(CASE WHEN product_type = '办公用品' THEN sale_price ELSE 0 END) AS sum_price_office
FROM product;

结果:

sum_price_clothes sum_price_kitchen sum_price_office
5000 11180 600
场景 3:行转列(实际开发高频)

行式数据 转换为列式数据,比如将学生的成绩行(姓名、科目、分数)转换为成绩表(姓名、语文、数学、外语),这是报表开发的核心需求。

示例:成绩行转列

sql

sql 复制代码
-- 原始成绩表(score):name, subject, score
-- 行转列后:name, chinese, math, english
SELECT name,
       SUM(CASE WHEN subject = '语文' THEN score ELSE NULL END) AS chinese,
       SUM(CASE WHEN subject = '数学' THEN score ELSE NULL END) AS math,
       SUM(CASE WHEN subject = '外语' THEN score ELSE NULL END) AS english
FROM score
GROUP BY name;

技巧

  • 数字列转列:用SUM/AVG/MAX/MIN(推荐 SUM);
  • 文本列转列:用MAX/MIN(不能用 SUM)。

六、本节课核心小结

今天学的 SQL 复杂查询知识点,不是孤立的,而是相互结合使用的,核心可以总结为 3 点:

  1. 视图 + 子查询 :解决查询逻辑复用问题,视图用于永久复用,子查询用于临时复用;
  2. 函数 + 谓词 :解决数据处理和条件筛选问题,函数提升数据处理效率,谓词实现精准的条件判断;
  3. CASE 表达式 :解决数据分类和格式转换问题,是 SQL 的 "万能工具",几乎所有复杂的取数 / 统计需求都离不开它。

这些知识点是SQL 进阶的基础 ,也是实际开发中每天都会用到 的内容,重点在于多练 :结合productshop_product表,把每个知识点的示例手动写一遍,再尝试组合使用(比如CASE+聚合+GROUP BYIN+子查询+谓词),很快就能熟练掌握。

相关推荐
崎岖Qiu1 小时前
Redis Set 实战:基于「并、差、交集」的分布式场景应用
数据库·redis·分布式·后端
PD我是你的真爱粉2 小时前
构建高可用的Redis 集群
数据库·redis·缓存
_OP_CHEN4 小时前
【MySQL数据库基础】(一)保姆级 MySQL 环境配置教程!CentOS 7+Ubuntu 双系统全覆盖
linux·数据库·sql·mysql·ubuntu·centos·环境配置
Drifter_yh10 小时前
【黑马点评】Redisson 分布式锁核心原理剖析
java·数据库·redis·分布式·spring·缓存
鸽鸽程序猿10 小时前
【Redis】zset 类型介绍
数据库·redis·缓存
z玉无心10 小时前
Redis
数据库·redis·oracle
予枫的编程笔记10 小时前
【Redis核心原理篇2】Redis 单线程模型:为什么单线程还能这么快?
数据库·redis·缓存
fengxin_rou10 小时前
一文吃透 Redis 压缩列表、listpack 及哈希表扩容与并发查询
数据库·redis·散列表
一只鹿鹿鹿10 小时前
智慧水利一体化建设方案
大数据·运维·开发语言·数据库·物联网