news 2026/10/31 2:30:28

MySQL数据查询入门:从SELECT骨架到多表JOIN实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL数据查询入门:从SELECT骨架到多表JOIN实战

简介:本资源是国家开放大学《MySQL数据库应用》课程配套实验训练材料,面向数据库初学者、高职高专及成人教育学员,系统训练SQL数据查询核心能力。内容覆盖字段查询、多条件筛选、DISTINCT去重、ORDER BY排序、GROUP BY分组、COUNT/SUM/AVG/MAX/MIN等聚合函数应用,以及内连接、外连接、复合条件连接与IN/EXISTS/比较运算符三类嵌套查询,全部基于汽车用品网上商城真实业务场景设计,含13个子实验共20+典型查询案例。资源为单文件PDF文档,共1个文件,大小1.75MB,排版清晰、分析详尽、语句可直接运行验证。目前已有3339人学习下载,适合作为课堂实训补充、课后巩固练习或自学SQL查询的结构化训练指南。

1. 别被“SQL查询”四个字吓住:它其实就是数据库里的“智能搜索框”

你有没有用过手机相册的“搜索”功能?输入“海边”,所有带海、带沙滩、带太阳的照片就自动跳出来;输入“2023年春节”,全家福、年夜饭、红包照立刻按时间排好队——你没翻相册一页页找,系统替你“想明白”了你要什么,然后精准打包给你。MySQL里的数据查询(Query),就是这个相册搜索框的“企业级加强版”:它不搜照片,而是搜成千上万条订单、用户、商品记录;它不靠模糊匹配,而是靠你亲手写的几行指令,告诉数据库“我要哪张表里的哪些字段”“满足什么条件才留下”“结果按什么顺序排”“重复的只留一个”。这份《国家开放大学 MySQL数据库应用 实验训练2:数据查询操作》,就是一套手把手带你调教这个“智能搜索框”的实战手册。它不讲服务器怎么装、不聊索引原理有多玄,就聚焦一件事:让你从零开始,写出第一句能跑通、能看懂、能改出新结果的SELECT语句。适合完全没碰过数据库的新人,也适合学过但总在“写完报错”和“结果不对”之间反复横跳的同学——因为里面每一个实验,都来自真实业务场景(比如查促销商品、算每天销售额、找最贵的商品),每一步分析都告诉你“为什么这么写”,而不是只甩一句“语法如此”。接下来,我们就从最基础的“查一条商品信息”开始,把那些看起来高冷的关键词(DISTINCT、GROUP BY、JOIN……)变成你键盘上信手拈来的工具。

2. 先跑通第一个查询:三步搞懂 SELECT 语句的骨架

刚接触SQL时,最容易卡在“不知道从哪下手”。看到SELECT * FROM products WHERE name = '挡风玻璃';这种句子,心里可能嘀咕:“星号是啥?WHERE后面为啥要加单引号?products表长啥样?”别急,我们拆成三步,用生活化动作来理解。

2.1 第一步:明确“去哪找”——FROM 子句就是你的文件柜编号

想象你办公室有三个文件柜:

  • 柜子A贴着“商品清单”标签(对应products表)
  • 柜子B贴着“订单记录”标签(对应orders表)
  • 柜子C贴着“用户资料”标签(对应users表)

当你想查“挡风玻璃”的信息,第一反应肯定是:“这东西属于商品,得去柜子A里翻!”——这就是FROM products的作用:它不负责找具体内容,只指定数据源在哪张表里。新手常犯的错是漏写FROM,或者写错表名(比如写成product少了个s)。记住:没有FROM,就像没告诉快递员收货地址,SQL根本不知道去哪取数据。

2.2 第二步:决定“拿什么”——SELECT 子句就是你的取件清单

柜子A里有几十个抽屉:商品ID、名称、价格、库存、类别ID、是否促销……你不需要全搬走,只拿关键信息。这时SELECT就是你的取件清单:

  • SELECT *:意思是“把柜子A里所有抽屉的东西全拿出来”(*星号代表‘全部字段’,适合初学看结构,但正式开发中尽量避免,效率低)
  • SELECT name, price, stock:意思是“只要名称、价格、库存这三个抽屉的东西”(精准高效,推荐养成习惯)
  • SELECT name AS 商品名称:给字段起个中文昵称,让结果更易读(AS可省略,直接写name 商品名称也行)

提示:初学时先用SELECT *看全貌,确认表结构后再精简字段。别一上来就死磕“必须写全字段”,那是熟手优化的事,不是入门门槛。

2.3 第三步:设定“挑哪些”——WHERE 子句就是你的筛选条件卡

柜子A里有1000个商品,你只想要“挡风玻璃”。这时WHERE就是你的筛选卡:

WHERE name = '挡风玻璃'

注意三个细节:

  1. 等号=是判断“完全相等”,不是赋值(编程语言里赋值常用=,但SQL里赋值用:=或SET,别混淆);
  2. 字符串值必须用单引号' '包裹(数字不用,比如WHERE id = 1);
  3. 字段名name区分大小写吗?在MySQL默认配置下不区分(NAME、name、Name都行),但表名在Linux服务器上区分——所以统一小写最安全。

现在,把三步拼起来,就是你的第一句可运行SQL:

SELECT * FROM products WHERE name = '挡风玻璃';

执行后,如果表里真有这条记录,你会看到一行或多行完整数据;如果没找到,结果为空(不是报错!这是正常情况)。空结果不等于写错了,它只是诚实地告诉你:“按你的要求,这里没有匹配项。”这种“无结果胜于报错”的设计,正是SQL的友好之处。

2.4 动手试一试:用真实数据验证你的理解

假设你已安装好MySQL(如MySQL Community Server 8.0+),并导入了实验配套的汽车用品商城数据库(含products、orders等表)。打开命令行或客户端(如MySQL Workbench),输入以下命令连接数据库:

mysql -u root -p # 输入密码后进入MySQL交互界面 USE car_shop_db; -- 切换到实验数据库(具体库名以你导入的为准)

然后粘贴执行:

SELECT id, name, price FROM products WHERE name = '挡风玻璃';

✅ 你看到的结果应该包含该商品的ID、名称、价格三列;
❌ 如果报错Unknown column 'name' in 'where clause',说明products表里没有叫name的字段——这时别硬改,用DESCRIBE products;查看真实字段名(可能是product_name或title);
❌ 如果返回空集,检查'挡风玻璃'是否和表中存储的字符完全一致(比如多了空格、用了全角字符、或实际存的是“前挡风玻璃”)。

这三步(FROM→SELECT→WHERE)就是SELECT语句的黄金骨架。后续所有高级操作,都是在这个骨架上加肌肉:加排序(ORDER BY)、加分组(GROUP BY)、加连接(JOIN)……但骨架不稳,肌肉再壮也会散架。

3. 为什么不能只写“查所有”?DISTINCT 和 ORDER BY 是让结果可读的关键

很多新手写完SELECT * FROM products;就以为任务完成,结果面对满屏重复数据发呆:“怎么有5条一模一样的‘车载充电器’?” 或者看着乱序的订单列表抓狂:“我想看最新下的单,为啥第一条是2019年的?”——这时候,DISTINCT和ORDER BY就是你整理战利品的两把梳子:一把去重,一把理顺。

3.1 DISTINCT:专治“怎么又是它!”的重复焦虑

回到实验2.3(1):“查询所有对商品ID为1的商品发表过评论的用户ID”。评论表(comments)长这样:

comment_idproduct_iduser_idcontent
11101好用!
21102不错
31101再买一个
41103一般

如果直接写SELECT user_id FROM comments WHERE product_id = 1;,结果会是:

101 102 101 103

用户101出现了两次(他评了两次),但业务需求是“有哪些人评过”,不是“评了多少次”。这时DISTINCT就像一个自动去重的筛子:

SELECT DISTINCT user_id FROM comments WHERE product_id = 1;

结果立刻变干净:

101 102 103

注意:DISTINCT作用于整行结果。如果写SELECT DISTINCT user_id, content,那(101, '好用!')和(101, '再买一个')就是两行不同数据,不会去重。所以去重前先想清楚:“我要按哪个字段去重?”

3.2 ORDER BY:让数据自己排队,告别手动翻找

实验2.4(1)要求:“查询类别ID为1的所有商品,按商品ID降序排列”。不加排序时,数据库返回顺序是它内部存储的物理顺序(可能按插入时间,也可能乱序),毫无业务意义。加上ORDER BY,就像给学生按身高排队:

SELECT id, name, price FROM products WHERE category_id = 1 ORDER BY id DESC;
  • ORDER BY id:按id字段升序(从小到大,默认行为,可省略ASC);
  • ORDER BY id DESC:加DESC(descending)变降序(从大到小);
  • ORDER BY price, name:先按价格升序,价格相同时再按名称字母升序(多级排序,用逗号分隔)。

血泪经验:新手常忽略ORDER BY,导致测试时结果“有时对有时错”。其实不是SQL错了,是数据库没承诺返回顺序——不显式排序,结果顺序就是不可预测的。把它当成呼吸一样自然:只要结果需要可读性,就加上排序。

3.3 组合技:DISTINCT + ORDER BY 解决真实业务痛点

实验2.3(2)问:“查询会员的创建时间段,1年为一段”。用户表(users)里有created_at字段(格式如'2022-03-15 10:22:33')。怎么提取“年份”?用MySQL内置函数YEAR():

SELECT YEAR(created_at) FROM users;

但这会返回一堆重复年份(2022年注册了100人,就显示100个2022)。于是组合拳来了:

SELECT DISTINCT YEAR(created_at) FROM users ORDER BY YEAR(created_at) DESC;

结果:

2023 2022 2021

✅DISTINCT去掉重复年份;
✅ORDER BY ... DESC让最新年份排最前;
✅YEAR()函数把日期转成数字,方便处理。

避坑 / 常见问题 / 排查

  1. 现象:SELECT DISTINCT name FROM products ORDER BY price;报错 “Expression #1 of ORDER BY clause is not in SELECT list”
    原因:ORDER BY里用了price字段,但SELECT只选了name,数据库不知道按哪个price排(一个名字可能对应多个价格)
    解决:要么把price加进SELECT(SELECT DISTINCT name, price),要么改用SELECT DISTINCT name FROM products ORDER BY name;(按选出来的字段排)

  2. 现象:SELECT DISTINCT user_id FROM comments WHERE product_id = 1;返回空,但明明有评论
    原因:product_id字段名写错(如写成productid或pid),或条件值1是字符串类型(应写WHERE product_id = '1')
    解决:先用SELECT * FROM comments LIMIT 5;看真实字段名和数据类型,再调整条件

  3. 现象:ORDER BY created_at DESC结果里,2023年的记录在最后,不是最前
    原因:created_at是字符串类型(如'2023/01/01'),不是日期类型(DATE或DATETIME),字符串排序按ASCII码,'2023' < '2022'因为'2'='2'后比'0'<'2'
    解决:用STR_TO_DATE(created_at, '%Y/%m/%d')转日期,或建表时就用DATE类型存

  4. 现象:DISTINCT后结果少了,但业务上需要知道“每个用户评了几次”
    原因:DISTINCT是去重,不是计数;要统计次数得用GROUP BY+COUNT()(下一章重点)
    解决:区分场景——要“名单”用DISTINCT,要“次数”用聚合函数

4. 从“查单条”升级到“算总数”:GROUP BY 和聚合函数是业务分析的起点

当老板问:“这个月每个销售员卖了多少单?”、“各类别商品平均价格多少?”、“哪天订单最多?”,你就不能再用SELECT * FROM orders了——那是一堆原始数据,而老板要的是汇总后的结论。这时,GROUP BY(分组)和聚合函数(COUNT、SUM、AVG…)就是你的计算器和分类箱:先把数据按规则分堆(如按销售员分堆),再对每堆算一个数(如每堆有多少单)。

4.1 GROUP BY:先分堆,再计算

实验2.5(1):“查询每个用户的消费总金额”。订单表(orders)有user_id和total_amount字段。目标是:

  • 用户101 → 总金额 ¥5680
  • 用户102 → 总金额 ¥3200
  • ……

怎么做?分两步:

  1. 分堆:用GROUP BY user_id把所有订单按user_id归类,相同user_id的订单进同一堆;
  2. 计算:对每一堆用SUM(total_amount)求和。

完整SQL:

SELECT user_id, SUM(total_amount) AS total_spent FROM orders GROUP BY user_id;

✅GROUP BY必须和SELECT中的非聚合字段一致(这里user_id在SELECT里出现,就必须GROUP BY user_id);
✅SUM()是聚合函数,它作用于每一堆内部,不是全表;
✅AS total_spent给计算结果起个易懂的名字。

4.2 五大聚合函数:COUNT/SUM/AVG/MAX/MIN,各司其职

实验2.6-2.10覆盖了最常用的五个函数,它们就像五种专用计算器:

函数用途实验例子关键注意
COUNT()数数量COUNT(*)数总行数;COUNT(user_id)数非NULL的user_id数COUNT(*)包含NULL行,COUNT(字段)忽略NULL值
SUM()求和SUM(total_amount)算总销售额字段必须是数字类型,否则返回0或报错
AVG()求平均AVG(total_amount)算平均订单额自动忽略NULL值,结果是浮点数
MAX()找最大MAX(price)找最贵商品;MAX(username)找字母序最后的用户名文本按字典序比较(Z>A),日期按时间先后
MIN()找最小MIN(price)找最便宜商品;MIN(created_at)找最早注册用户同上,文本/日期均可直接用

动手验证:在你的数据库中执行:

-- 实验2.6(1):查类别数量(假设类别表叫 categories) SELECT COUNT(*) AS category_count FROM categories; -- 实验2.8(1):所有订单平均金额 SELECT AVG(total_amount) AS avg_order FROM orders; -- 实验2.9(2):用户名字字母序最靠前者(按ASCII,A最小) SELECT MIN(username) AS first_user FROM users;

4.3 多列分组:解决“既要又要”的复杂需求

实验2.5(2):“查询类别价格一样的各种商品数量总和”。这需要同时按两个字段分组:

  • 先按category_id分大堆(汽车类、电子类……);
  • 再在每个大堆里,按price分小堆(100元堆、200元堆……);
  • 最后对每个小堆用COUNT(*)算数量。

SQL长这样:

SELECT category_id, price, COUNT(*) AS quantity FROM products GROUP BY category_id, price ORDER BY category_id, price;

结果示例:

category_idpricequantity
180.005
1120.003
250.0012

✅GROUP BY后跟多个字段,用逗号分隔;
✅SELECT中的非聚合字段(category_id,price)必须全部出现在GROUP BY中;
✅ORDER BY可独立于分组字段,这里按类别和价格排序,让结果更清晰。

避坑 / 常见问题 / 排查

  1. 现象:SELECT user_id, SUM(total_amount) FROM orders;报错 “Invalid use of group function”
    原因:用了聚合函数SUM(),但没写GROUP BY,数据库不知道“对谁求和”
    解决:要么加GROUP BY user_id,要么去掉user_id只留SUM(total_amount)(算全表总和)

  2. 现象:GROUP BY后结果里,某个user_id对应的SUM(total_amount)是0,但查他订单发现有金额
    原因:total_amount字段是字符串类型(如'120.00'),SUM()无法计算,返回0;或该用户有订单但total_amount为NULL
    解决:用DESCRIBE orders;查字段类型,确保是DECIMAL;用WHERE total_amount IS NOT NULL过滤

  3. 现象:COUNT(*)和COUNT(id)结果不同
    原因:id字段有NULL值(虽然主键不该有,但其他字段可能有),COUNT(id)忽略NULL,COUNT(*)统计所有行
    解决:确认业务需求——要“总行数”用COUNT(*),要“非空ID数”用COUNT(id)

  4. 现象:AVG(price)结果是整数(如150),但期望小数(150.25)
    原因:price字段定义为INT,MySQL对整数求平均会截断小数;或AVG()结果被CAST转成了整数
    解决:SELECT AVG(CAST(price AS DECIMAL(10,2)))强制转精度,或建表时用DECIMAL类型

5. 当数据分散在多个柜子时:JOIN 连接查询是打通任督二脉的关键

前面所有操作都在单张表里完成,像在柜子A里翻找商品。但真实业务中,信息是分散的:订单在柜子B(orders),用户姓名在柜子C(users),你想查“订单ID为1001的客户叫什么”,就得同时打开柜子B和柜子C,并把它们关联起来。这就是JOIN(连接)的使命——它不是复制数据,而是临时“搭桥”,让不同表的数据能互相认出对方。

5.1 内连接(INNER JOIN):只保留双方都有的记录

实验2.11(1):“查询所有订单的发出者名字”。orders表有user_id,users表有id和username。如何关联?用user_id和id这两个值相等的记录“配对”:

SELECT o.order_id, u.username FROM orders AS o INNER JOIN users AS u ON o.user_id = u.id;
  • AS o/AS u:给表起短名,写起来不累(o.user_id比orders.user_id简洁);
  • ON o.user_id = u.id:连接条件,orders表的user_id必须等于users表的id;
  • 结果只包含“既有订单又有对应用户”的记录。如果某订单的user_id=999,但users表里没有id=999的用户,这条订单就被过滤掉了。

5.2 外连接(LEFT/RIGHT JOIN):宁可留空,也不丢记录

实验2.12(1):“列出所有用户ID,以及他们的评论(如果有的话)”。需求变了:要所有用户,哪怕没人评论也要显示ID,评论字段为空就行。这时内连接不行(没评论的用户会被丢掉),得用外连接:

SELECT u.id AS user_id, c.content FROM users AS u LEFT JOIN comments AS c ON u.id = c.user_id;
  • LEFT JOIN:以LEFT边的表(users)为主,保证它所有记录都出现;
  • c.content是右表字段,没匹配时显示NULL;
  • 结果示例:
    user_idcontent
    101好用!
    102NULL

RIGHT JOIN逻辑相反(以右表为主),但用LEFT JOIN更符合阅读习惯(主表放左边),实际开发中几乎不用RIGHT。

5.3 复合条件连接:在连接基础上再加筛选

实验2.13(1):“查询用户ID为1的客户的订单信息和客户名”。这需要两步过滤:

  1. 先连接orders和users;
  2. 再筛选user_id = 1。

两种写法等价,但推荐把条件放在WHERE(更清晰):

SELECT o.order_id, o.total_amount, u.username FROM orders AS o INNER JOIN users AS u ON o.user_id = u.id WHERE o.user_id = 1; -- 这里加条件

如果写在ON里:

... ON o.user_id = u.id AND o.user_id = 1 -- 效果相同,但可读性差

原则:连接条件(ON)定义“两张表如何配对”,业务条件(WHERE)定义“配对后要哪些”。混在一起容易逻辑混乱。

5.4 动手连一连:从购物车看到商品名

实验2.11(2):“查询每个用户购物车中的商品名称”。涉及三张表:

  • cart(购物车):user_id,product_id,quantity
  • products(商品):id,name,price
  • 目标:user_id,product_name

步骤:

  1. 连接cart和products,条件cart.product_id = products.id;
  2. 选择需要的字段。

SQL:

SELECT c.user_id, p.name AS product_name, c.quantity FROM cart AS c INNER JOIN products AS p ON c.product_id = p.id;

执行后,你会看到每个用户买了什么商品、买了几个。这就是JOIN的价值:把碎片信息拼成完整故事——没有它,你只能知道“用户101买了商品ID=5”,却不知道商品ID=5到底是“车载支架”还是“蓝牙耳机”。

避坑 / 常见问题 / 排查

  1. 现象:SELECT * FROM orders INNER JOIN users ON orders.user_id = users.id;返回大量重复列(如id在orders和users里都有)
    原因:SELECT *会把两张表所有字段都选出来,同名字段冲突
    解决:明确指定字段(SELECT o.order_id, u.username),或用表别名限定(SELECT o.id, u.id)

  2. 现象:LEFT JOIN结果里,左表字段正常,右表字段全是NULL
    原因:连接条件写错(如ON u.id = c.user_id写成ON u.id = c.product_id),导致无法匹配
    解决:先单独查SELECT * FROM users LIMIT 3;和SELECT * FROM comments LIMIT 3;,确认字段名和值是否能对应

  3. 现象:连接后行数暴增(如100行变10000行)
    原因:连接条件不唯一,造成笛卡尔积(一个用户对应10条评论,就生成10行;100用户×10评论=1000行)
    解决:检查连接字段是否有重复值(如user_id在comments表里是外键,应有索引且无脏数据)

  4. 现象:INNER JOIN结果为空,但确认两张表都有数据
    原因:数据类型不匹配(如orders.user_id是VARCHAR,users.id是INT,字符串'1' ≠ 数字1)
    解决:用SELECT o.user_id, u.id, o.user_id = u.id FROM orders o, users u LIMIT 5;测试相等性,再用CAST(o.user_id AS SIGNED)转类型

6. 当“先查A再查B”变成刚需:嵌套查询是解决复杂逻辑的后悔药

前面的JOIN很强大,但它要求“两张表能直接关联”。但有些问题像解谜题:“找出所有今年注册的用户,然后查他们下的订单中总价小于100的有哪些”。这里,“今年注册的用户”是一个中间结果,不是一张现成的表——你需要先算出A,再用A去查B。这就是嵌套查询(子查询):把一个SELECT语句当做一个值,塞进另一个SELECT的条件里。

6.1 IN:用“名单”做条件,最直观的子查询

实验2.14(1):“查询订购商品ID为1的订单ID,并根据订单ID查询发出此订单的用户ID”。分两步:

  1. 子查询:SELECT order_id FROM order_items WHERE product_id = 1→ 得到订单ID列表(如[1001, 1005, 1008]);
  2. 主查询:SELECT user_id FROM orders WHERE order_id IN (1001, 1005, 1008)。

合并成一句:

SELECT user_id FROM orders WHERE order_id IN ( SELECT order_id FROM order_items WHERE product_id = 1 );

✅ 子查询用括号包裹,可以独立运行;
✅IN后面跟一个结果集(多行单列),相当于“在这些值里找”;
✅NOT IN逻辑相反(不在名单里的)。

6.2 EXISTS:只关心“有没有”,不关心“有哪些”

实验2.16(1):“查询是否存在用户ID为100的用户,如果存在,列出此用户信息”。EXISTS不返回具体值,只返回TRUE/FALSE:

SELECT * FROM users WHERE EXISTS ( SELECT 1 FROM users WHERE id = 100 );
  • 子查询SELECT 1是固定写法(返回任意常量,效率高);
  • EXISTS只检查子查询是否返回至少一行,不关心内容;
  • 如果存在ID=100的用户,主查询就执行SELECT * FROM users(返回所有用户);
  • 但通常我们会用相关子查询,让内外表关联:
SELECT u.* FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.total_amount > 500 );

→ 查所有下过500元以上订单的用户。o.user_id = u.id是关联条件,让子查询依赖主查询的每一行。

6.3 ANY/ALL:和子查询结果“逐个比”

实验2.17 & 2.18 涉及ANY和ALL,它们用于和子查询返回的多个值比较:

  • price > ANY (SELECT price FROM orders):价格比子查询结果中任意一个值大(即比最小值大);
  • price > ALL (SELECT price FROM orders):价格比子查询结果中所有值都大(即比最大值还大)。

例如,找比所有订单总价都高的商品:

SELECT name, price FROM products WHERE price > ALL ( SELECT total_amount FROM orders );

⚠️ 注意:ALL子查询结果不能有NULL,否则整个条件为UNKNOWN(结果为空)。安全写法:

WHERE price > ALL (SELECT total_amount FROM orders WHERE total_amount IS NOT NULL)

6.4 下一步学什么:从“会写”到“写得好”的三步跃迁

学到这里,你已经能写出实验2中90%的查询语句:单表查、去重、排序、分组统计、多表连接、简单嵌套。但真实项目里,光“能跑通”不够,还要“跑得快”“改得顺”“查得准”。我的血泪经验是,接下来三个月重点攻克这三件事:

第一步:给常用字段加索引,让查询从“等10秒”变“秒出”

你写完SELECT * FROM orders WHERE user_id = 101;,如果user_id没索引,数据库得扫全表(100万行就扫100万次)。加索引只需一句:

CREATE INDEX idx_orders_user_id ON orders(user_id);

✅ 哪些字段加索引?WHERE、JOIN ON、ORDER BY后的字段;
✅ 避免过度索引:每加一个索引,写入速度会慢一点,磁盘占用多一点;
✅ 工具验证:用EXPLAIN SELECT ...看执行计划,type显示ref或range表示走了索引,ALL表示全表扫描。

第二步:用视图(VIEW)封装复杂查询,让同事也能看懂

把实验2.13(2)“每个用户购物车商品价格排序”这种多表连接SQL,存成一个虚拟表:

CREATE VIEW user_cart_prices AS SELECT c.user_id, p.name, p.price FROM cart c INNER JOIN products p ON c.product_id = p.id;

之后,同事查价格只需:

SELECT * FROM user_cart_prices WHERE price < 100 ORDER BY price;

✅ 视图不存数据,只存SQL定义,修改定义即可全局生效;
✅ 权限控制:可以只给同事查视图的权限,不给查原表的权限,更安全。

第三步:学点基础SQL注入防护,别让“万能密码”攻破你的登录

你写的SELECT * FROM users WHERE username = '$input',如果$input是' OR '1'='1,整句变WHERE username = '' OR '1'='1'→ 永远为真!解决方案只有两个:

  • 永远用预处理语句(Prepared Statement):参数和SQL分离,数据库先编译SQL再填值;
  • 永远不用字符串拼接SQL:哪怕只是WHERE id = $_GET['id'],也必须转为整数或白名单校验。

从那以后我每次写查询,都强制走一遍 checklist:

  1. 字段名、表名是否拼写正确?(用DESCRIBE table;确认)
  2. 字符串条件是否加单引号?数字是否没加?
  3. 用了聚合函数,是否写了GROUP BY?
  4. 多表连接,是否指定了ON条件?
  5. 涉及用户输入,是否用了预处理?

这五条看似琐碎,但覆盖了95%的新手翻车现场。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 2:30:27

TensorFlow实现猴痘皮肤病变图像识别的CNN模型

1. 项目背景与核心目标最近在医疗影像识别领域&#xff0c;基于深度学习的自动化诊断技术正逐渐成为研究热点。这次我们要实现的是一个具有实际应用价值的项目——使用TensorFlow框架构建卷积神经网络(CNN)模型&#xff0c;实现对猴痘病皮肤病变图像的自动识别。这个项目特别适…

作者头像 李华
网站建设 2026/10/31 2:29:38

深度强化学习在人工电磁材料逆向设计中的应用

1. 项目背景与核心价值人工电磁材料&#xff08;Metamaterials&#xff09;作为21世纪最具颠覆性的材料科学突破之一&#xff0c;其核心在于通过亚波长尺度的结构设计实现传统材料无法达到的电磁特性。这种"结构决定功能"的特性使其在隐身技术、超分辨率成像、新型天…

作者头像 李华
网站建设 2026/10/31 2:29:37

JavaWeb铁路接发车培训系统设计与实现

1. 项目概述"JavaWeb铁路火车接发车课程作业培训考试系统证书"是一个面向铁路运输专业教学与考核的综合性管理系统。这个系统将铁路运输中的核心业务场景——火车接发车作业流程数字化&#xff0c;为铁路相关专业的学生和从业人员提供课程学习、作业提交、在线培训和…

作者头像 李华
网站建设 2026/10/31 2:29:12

电商消费者行为分析与深度学习预测模型实践

1. 项目背景与核心目标电商平台每天产生海量的消费者行为数据&#xff0c;这些数据中隐藏着宝贵的商业洞察。本项目旨在通过数据挖掘技术&#xff0c;从电商平台的食品消费数据中提取有价值的消费者偏好信息&#xff0c;并建立预测模型分析购买行为模式。核心目标包括&#xff…

作者头像 李华
网站建设 2026/10/31 2:27:57

基数排序算法原理与实现详解

1. 基数排序算法解析基数排序&#xff08;Radix Sort&#xff09;是一种非比较型整数排序算法&#xff0c;它通过逐位比较数字的每一位来实现排序。与常见的比较排序算法&#xff08;如快速排序、归并排序&#xff09;不同&#xff0c;基数排序不直接比较元素的大小&#xff0c…

作者头像 李华
网站建设 2026/10/31 2:26:19

Windows下Nexus 3.30私服搭建与优化实战

简介&#xff1a;Nexus 3.30.0-01 的 Windows 64 位安装包&#xff0c;面向需要在本地或企业内网搭建 Maven 仓库的 Java 开发者与运维人员&#xff0c;用于解决依赖下载慢、组件发布分散、仓库权限难统一等问题。压缩包约 212.01MB&#xff0c;内含 sonatype-work 与 nexus-3.…

作者头像 李华