SQL 语法详细解析(逐行注释版)
以下是对这份 SQL 学习笔记中所有代码片段的逐行详细解释,涵盖基础查询、连接、增删改查、函数等核心知识点,帮助理解每行代码的作用和底层逻辑。
一、基础查询(SELECT 语句)
1. 基础 SELECT 结构
sql
USE sql_store; -- 切换到名为sql_store的数据库,后续操作都基于该库
SELECT * -- 选择表中所有列(* 是通配符,代表所有字段)
FROM customers -- 指定数据来源为customers表
WHERE customer_id=1 -- 筛选条件:只返回customer_id等于1的行
ORDER BY first_name -- 排序规则:按first_name列的字母顺序升序排列
2. 列别名(含空格)
sql
SELECT first_name, -- 选择customers表的first_name列
last_name, -- 选择customers表的last_name列
points, -- 选择customers表的points列
points*10+100 AS 'discount factor' -- 计算points*10+100,并将结果列命名为'discount factor'(别名含空格需加单引号)
FROM customers -- 数据来源:customers表
3. 去重查询(DISTINCT)
sql
SELECT DISTINCT state -- 选择state列,并去除重复值(只保留唯一的state值)
FROM customers -- 数据来源:customers表
二、筛选条件(WHERE 子句)
1. 等值/不等值判断
sql
SELECT *
FROM customers
WHERE state='VA' -- 筛选state等于'VA'的行(字符串值需加单引号)
sql
WHERE state!='VA' -- 筛选state不等于'VA'的行(! = 是不等号)
sql
SELECT *
FROM customers
WHERE state<>'va' -- 筛选state不等于'va'的行(<> 等价于! =,是标准SQL的不等号)
2. 日期筛选
sql
SELECT *
FROM customers
WHERE birth_date>'1990-01-01' -- 筛选出生日期在1990年1月1日之后的行(日期值需加单引号,格式为YYYY-MM-DD)
3. 多条件组合(AND/OR/NOT)
sql
SELECT *
FROM customers
WHERE birth_date>'1990-01-01' OR points>1000 AND state='va'
-- 逻辑组合:(birth_date>1990-01-01) 或者 (points>1000 且 state='va')
-- 优先级:AND > OR,若需调整优先级需加括号
sql
SELECT *
FROM customers
-- 注释:NOT 取反括号内的条件,等价于 birth_date<='1990-01-01' AND points<=1000
WHERE NOT (birth_date>'1990-01-01' OR points>1000)
-- 筛选:既不满足birth_date>1990-01-01,也不满足points>1000的行
4. 范围筛选(IN/BETWEEN)
sql
SELECT *
FROM customers
WHERE state NOT IN ('VA','GA','FL')
-- 筛选state不在('VA','GA','FL')这个列表中的行(IN用于匹配列表值,NOT IN取反)
sql
SELECT *
FROM customers
WHERE points BETWEEN 1000 AND 3000
-- 筛选points在1000到3000之间的行(包含边界值,等价于 points>=1000 AND points<=3000)
5. 模糊查询(LIKE)
sql
SELECT *
FROM customers
WHERE first_name LIKE '%B%'
-- 模糊匹配:first_name中包含字母B的行(% 是通配符,代表任意多个任意字符)
-- 补充:'%B' 以B结尾,'B%' 以B开头,'_y' 匹配两个字符且第二个是y(_ 代表单个任意字符)
6. 正则匹配(REGEXP)
sql
-- 补充示例(原笔记仅文字说明,补全代码)
SELECT *
FROM customers
WHERE first_name REGEXP '^B' -- 匹配first_name以B开头的行(^ 代表开头)
WHERE first_name REGEXP 'y$' -- 匹配first_name以y结尾的行($ 代表结尾)
WHERE first_name REGEXP 'B|Mac' -- 匹配first_name包含B或Mac的行(| 代表或)
WHERE first_name REGEXP '[gim]e' -- 匹配first_name包含ge/ie/me的行([gim] 匹配g/i/m中的任意一个)
WHERE first_name REGEXP '[a-h]e' -- 匹配first_name包含ae/be/.../he的行([a-h] 匹配a到h的任意字母)
7. 空值判断(IS NULL/IS NOT NULL)
sql
-- 补充示例(原笔记仅文字说明,补全代码)
SELECT *
FROM customers
WHERE phone IS NULL -- 筛选phone列为空的行(空值不能用=判断,必须用IS NULL)
WHERE phone IS NOT NULL -- 筛选phone列不为空的行
三、排序(ORDER BY 子句)
1. 基础排序/降序
sql
SELECT *
FROM customers
ORDER BY first_name -- 按first_name列升序排列(默认升序,ASC可省略)
sql
SELECT *
FROM customers
ORDER BY first_name DESC -- 按first_name列降序排列(DESC 代表降序)
2. 多列排序
sql
SELECT *
FROM customers
ORDER BY state,first_name
-- 先按state列升序,若state相同,再按first_name列升序(多列排序按逗号分隔的顺序优先级递减)
3. 按别名/列序号排序
sql
SELECT first_name,last_name,10+3 AS points -- 计算10+3并命名为points列
FROM customers
ORDER BY points,state
-- 按别名points列升序,再按state列升序(ORDER BY 可使用SELECT中的别名)
sql
SELECT first_name,last_name,10+3 AS points
FROM customers
ORDER BY 1,2
-- 按SELECT中第1列(first_name)、第2列(last_name)升序(列序号从1开始)
4. 业务场景排序
sql
SELECT *,quantity*unit_price AS sum_price -- 计算每行的总金额(数量*单价),命名为sum_price
FROM order_items
WHERE order_id=2 -- 筛选order_id为2的行
ORDER BY sum_price DESC -- 按总金额降序(展示订单2中金额最高的商品)
四、限制结果行数(LIMIT 子句)
1. 基础 LIMIT
sql
SELECT * FROM
sql_store.customers -- 完整表名:数据库名.表名(避免多库冲突)
LIMIT 3 -- 只返回前3行数据
2. 带偏移量的 LIMIT
sql
SELECT * FROM
sql_store.customers
LIMIT 6,3
-- 偏移量6(跳过前6行),返回后续3行(即第7、8、9行)
-- 语法:LIMIT 偏移量, 行数(注意:偏移量从0开始,LIMIT 3 等价于 LIMIT 0,3)
3. 排序后取TopN
sql
SELECT * FROM
sql_store.customers
ORDER BY points DESC -- 先按points降序
LIMIT 3 -- 取前3行(即points最高的3个客户)
五、表连接(JOIN)
1. 内连接(INNER JOIN,简写JOIN)
sql
SELECT order_id,orders.customer_id,first_name,last_name
FROM orders -- 主表:orders(订单表)
JOIN customers -- 连接表:customers(客户表)
ON orders.customer_id=customers.customer_id
-- 连接条件:订单表的customer_id等于客户表的customer_id(匹配两表的关联字段)
-- 作用:查询订单信息并关联对应的客户姓名(只返回两表匹配成功的行)
2. 表别名
sql
SELECT order_id,o.customer_id,first_name,last_name
FROM orders o -- 给orders表起别名o
JOIN customers c -- 给customers表起别名c
ON o.customer_id=c.customer_id
-- 别名生效后,所有表引用必须用别名(避免重复字段歧义)
3. 多表连接(订单商品表+商品表)
sql
SELECT order_id,p.product_id,quantity,o.unit_price,p.unit_price
FROM order_items o -- 订单商品表(别名o)
JOIN products p -- 商品表(别名p)
ON o.product_id=p.product_id
-- 连接条件:订单商品表的product_id等于商品表的product_id
4. 跨数据库连接
sql
SELECT *
FROM order_items oi -- 当前库的order_items表(别名oi)
JOIN sql_inventory.products p
-- 连接sql_inventory数据库的products表(跨库需指定:数据库名.表名)
ON oi.product_id=p.product_id
5. 自连接(员工表+自身,查上级)
sql
USE sql_hr; -- 切换到sql_hr数据库
SELECT
e.employee_id, -- 员工ID
e.first_name, -- 员工名
e.last_name, -- 员工姓
m.first_name AS manager -- 上级的名字(将m表的first_name别名化为manager)
FROM employees e -- 员工表(别名e,代表普通员工)
JOIN employees m -- 员工表(别名m,代表上级)
ON m.employee_id=e.reports_to
-- 连接条件:上级的employee_id等于员工的reports_to(上级ID字段)
6. 外连接(LEFT JOIN,保留左表所有行)
sql
USE sql_store;
SELECT p.product_id,
p.name,
oi.quantity
FROM products p -- 左表:商品表(保留所有商品)
LEFT JOIN order_items oi -- 右表:订单商品表
ON p.product_id=oi.product_id
-- 连接条件:商品ID匹配;即使商品未被下单(oi无匹配行),商品信息仍会返回(oi字段为NULL)
7. 多表外连接
sql
USE sql_store;
SELECT c.customer_id,
c.first_name,
o.order_id,
sh.name
FROM customers c -- 左表1:客户表(保留所有客户)
LEFT JOIN orders o -- 左连接订单表(客户无订单时,o字段为NULL)
ON c.customer_id=o.customer_id
LEFT JOIN shippers sh -- 左连接配送商表(订单无配送商时,sh字段为NULL)
ON o.shipper_id=sh.shipper_id
ORDER BY c.customer_id -- 按客户ID排序
8. 复合连接条件(多主键匹配)
sql
USE sql_store;
SELECT *
FROM order_items oi -- 订单商品表
JOIN order_item_notes oin -- 订单商品备注表
ON oi.order_id=oin.order_id AND oi.product_id=oin.product_id
-- 连接条件:同时匹配订单ID和商品ID(两表的复合主键),避免一对多错误匹配
9. USING 子句(简化连接条件)
sql
USE sql_store;
SELECT o.order_id,
c.first_name,
s.name AS shipper
FROM orders o
JOIN customers c
USING (customer_id)
-- 当两表连接字段名完全相同时,可用USING(字段名)替代ON o.customer_id=c.customer_id
LEFT JOIN shippers s
USING (shipper_id) -- 同理,简化shipper_id的连接条件
10. 自然连接(NATURAL JOIN)
sql
USE sql_invoicing;
SELECT p.date,
c.name AS client,
p.amount
FROM payments p
NATURAL JOIN clients c
-- 自然连接:自动匹配两表中名称相同的字段(如client_id),无需手动写ON/USING
-- 注意:风险高,若字段名相同但语义不同会导致错误匹配,不推荐使用
11. 交叉连接(CROSS JOIN)
sql
USE sql_store;
SELECT p.name AS product_name,
s.name AS shipper
FROM products p
CROSS JOIN shippers s
-- 交叉连接:返回两表的笛卡尔积(商品表每行匹配配送商表每行)
-- 场景:生成商品-配送商的所有组合
六、联合查询(UNION)
1. 订单状态分类
sql
USE sql_store;
SELECT order_id,
order_date,
'Active' AS status -- 新增列status,值为'Active'
FROM orders
WHERE order_date>='2019-01-01' -- 2019年及以后的订单标记为Active
UNION -- 合并两个查询结果(列数、列类型必须一致)
SELECT order_id,
order_date,
'Archived' AS status -- 新增列status,值为'Archived'
FROM orders
WHERE order_date<'2019-01-01' -- 2019年之前的订单标记为Archived
2. 客户等级分类
sql
USE sql_store;
SELECT customer_id,
first_name,
points,
'Gold' AS type -- 积分>3000标记为Gold
FROM customers
WHERE points>'3000'
UNION
SELECT customer_id,
first_name,
points,
'Bronze' AS type -- 积分<2000标记为Bronze
FROM customers
WHERE points<'2000'
UNION
SELECT customer_id,
first_name,
points,
'Silver' AS type -- 积分2000-3000标记为Silver
FROM customers
WHERE points BETWEEN '2000' AND '3000'
ORDER BY first_name -- UNION结果统一排序(只能写在最后)
七、数据插入(INSERT)
1. 单行插入(指定所有列)
sql
USE sql_store;
INSERT INTO customers -- 插入到customers表
VALUES (DEFAULT, -- customer_id(自增列)用DEFAULT自动生成
'John', -- first_name列值
'Smith', -- last_name列值
'1990-01-01', -- birth_date列值
NULL, -- phone列值(空值)
'address',-- address列值
'city', -- city列值
'CA', -- state列值
DEFAULT -- 其他默认列(如积分)用DEFAULT
)
2. 单行插入(指定部分列)
sql
USE sql_store;
INSERT INTO customers( -- 指定要插入的列(未指定的列用默认值/NULL)
first_name,
last_name,
birth_date,
address,
city,
state
)
VALUES ( -- 列值顺序必须与指定列一致
'John',
'Smith',
'1990-01-01',
'address',
'city',
'CA'
)
3. 多行插入
sql
USE sql_store;
INSERT INTO shippers (name) -- 插入到shippers表的name列
VALUES ('shipper1'), -- 第一行值
('shipper2') -- 第二行值(多行用逗号分隔)
4. 从查询结果插入(复制数据)
sql
USE sql_store;
-- CREATE TABLE orders_srchived AS -- 先创建新表orders_srchived,结构与查询结果一致(注释掉表示已创建)
-- SELECT * FROM orders
INSERT INTO orders_srchived -- 插入到orders_srchived表
SELECT * -- 从orders表查询所有列
FROM orders
WHERE
order_date<'2019-01-01' -- 筛选2019年前的订单,插入到新表
八、数据更新(UPDATE)
1. 更新单行
sql
USE sql_invoicing;
ALTER TABLE invoices
MODIFY COLUMN payment_total DECIMAL(20, 3); -- 修改payment_total列类型为DECIMAL(20,3)(20位总长度,3位小数)
UPDATE invoices -- 更新invoices表
SET payment_total=invoice_total*0.5, -- 将payment_total设为invoice_total的50%
payment_date=due_date -- 将payment_date设为due_date的值
WHERE invoice_id=3 -- 只更新invoice_id=3的行(必加WHERE,否则更新全表)
2. 更新多行
sql
USE sql_invoicing;
ALTER TABLE invoices
MODIFY COLUMN payment_total DECIMAL(20, 3);
UPDATE invoices
SET payment_total=invoice_total*0.5,
payment_date=due_date
WHERE client_id=3 -- 更新client_id=3的所有行
3. 子查询更新
sql
USE sql_store;
UPDATE orders
SET comments='Gold Customer' -- 将comments列设为'Gold Customer'
WHERE customer_id IN -- 筛选customer_id在子查询结果中的行
(
SELECT customer_id -- 子查询:找出积分>=3000的客户ID
FROM customers
WHERE points>=3000
)
九、聚合函数(统计计算)
sql
SELECT MAX(invoice_total) AS highest, -- 计算invoice_total的最大值,别名highest
MIN(invoice_total) AS lawest, -- 计算invoice_total的最小值,别名lawest
AVG(invoice_total) AS average, -- 计算invoice_total的平均值,别名average
SUM(invoice_total*1.1) AS sumest, -- 计算invoice_total*1.1的总和,别名sumest
-- COUNT(invoice_total) AS number_of_invoices, -- 统计invoice_total非空的行数
COUNT(DISTINCT client_id) AS count_of_payments, -- 统计不重复的client_id数量(去重计数)
COUNT(*) AS total_records -- 统计总行数(* 包含NULL值)
FROM invoices -- 数据来源:invoices表
十、分组查询(GROUP BY + HAVING)
1. 单列分组
sql
SELECT invoice_id,
SUM(invoice_total) AS total_sales -- 按client_id分组后,计算每组的invoice_total总和
FROM invoices
WHERE invoice_date>='2019-07-01' -- 先筛选2019-07-01后的行
GROUP BY client_id -- 按client_id分组(相同client_id的行归为一组)
ORDER BY total_sales DESC -- 按分组总和降序
2. 多列分组
sql
SELECT p.date,
pm.name,
p.amount
FROM payments p
JOIN payment_methods pm
ON p.payment_method=pm.payment_method_id
GROUP BY p.date,pm.name -- 先按date分组,同date内再按pm.name分组
3. 分组后筛选(HAVING)
sql
USE sql_store;
SELECT
c.customer_id,
c.first_name,
c.last_name,
SUM(oi.quantity*unit_price) AS number_price -- 计算客户的总消费金额
FROM customers c
JOIN orders o
USING(customer_id) -- 连接订单表
JOIN order_items oi
USING(order_id) -- 连接订单商品表
WHERE c.state='VA' -- 先筛选弗吉尼亚州的客户
GROUP BY customer_id -- 按客户ID分组
HAVING number_price>100 -- 筛选总消费>100的分组(HAVING针对分组结果筛选,WHERE针对原始行)
4. ROLLUP 汇总
sql
SELECT name,
SUM(amount) -- 按name分组求和
FROM payments p
JOIN payment_methods pm
ON p.payment_method=pm.payment_method_id
GROUP BY name WITH ROLLUP
-- WITH ROLLUP:在分组结果后追加一行总计(所有name的amount总和)
十一、子查询
1. 标量子查询(返回单个值)
sql
SELECT *
FROM employees
WHERE salary>
(
SELECT AVG(salary) -- 子查询:计算所有员工的平均工资
FROM employees
)
-- 筛选工资高于平均工资的员工
2. 相关子查询(内外查询关联)
sql
SELECT *
FROM employees e
WHERE salary>
(
SELECT AVG(salary)
FROM employees
WHERE office_id=e.office_id -- 子查询的office_id与外部e.office_id关联
)
-- 筛选:工资高于其所在办公室平均工资的员工
3. EXISTS 子查询(判断存在性)
sql
SELECT *
FROM clients c
WHERE EXISTS
(
SELECT client_id
FROM invoices
WHERE client_id=c.client_id
)
-- 筛选:有至少一张发票的客户(EXISTS只判断子查询是否有结果,不返回数据,效率高)
十二、函数
1. 数值函数
sql
SELECT ROUND(5.87888,2); -- 四舍五入:保留2位小数,结果5.88
SELECT TRUNCATE(5.87888,2); -- 截断:保留2位小数,结果5.87(不四舍五入)
SELECT CEILING(5.87888); -- 向上取整:结果6
SELECT FLOOR(5.87888); -- 向下取整:结果5
SELECT ABS(-5.87888); -- 绝对值:结果5.87888
SELECT RAND(); -- 生成0-1之间的随机数(每次执行结果不同)
2. 字符串函数
sql
SELECT LENGTH('jsjsj'); -- 字符串长度:结果5
SELECT UPPER('jsjsj'); -- 转大写:结果JSJSJ
SELECT LOWER('SHSJSJS'); -- 转小写:结果shsjsjs
SELECT LTRIM(' SHSHHS'); -- 去除左侧空格:结果SHSHHS
SELECT RTRIM('SHSHHS '); -- 去除右侧空格:结果SHSHHS
SELECT TRIM(' SHSHSH '); -- 去除两侧空格:结果SHSHSH
SELECT LEFT('asdfghjkl',4); -- 取左侧4个字符:结果asdf
SELECT RIGHT('asdfghjkl',4); -- 取右侧4个字符:结果hjkl
SELECT SUBSTR('asdfghjkl',3,5); -- 从第3位开始取5个字符:结果dfghj(索引从1开始)
SELECT LOCATE('dfg','asdfghjkl'); -- 查找子串位置:结果3(找不到返回0)
SELECT REPLACE('asdfghjkl','fgh','aa'); -- 替换子串:结果asdaajkl
SELECT CONCAT('abc','def'); -- 拼接字符串:结果abcdef
3. 日期函数
sql
SELECT NOW(),CURDATE(),CURTIME();
-- NOW():当前日期+时间(如2024-05-20 10:00:00)
-- CURDATE():当前日期(如2024-05-20)
-- CURTIME():当前时间(如10:00:00)
SELECT YEAR(NOW()); -- 提取年份:如2024
SELECT MONTH(NOW()); -- 提取月份:如5
SELECT DAY(NOW()); -- 提取日期:如20
SELECT HOUR(NOW()); -- 提取小时:如10
SELECT MINUTE(NOW()); -- 提取分钟:如0
SELECT SECOND(NOW()); -- 提取秒数:如0
SELECT DAYNAME(NOW()); -- 星期名称:如Monday
SELECT MONTHNAME(NOW()); -- 月份名称:如May
SELECT EXTRACT(DAY FROM NOW()); -- 提取指定部分(等价于DAY(NOW()))
SELECT DATE_FORMAT(NOW(),'%Y:%m:%d %H:%i:%s') AS time;
-- 格式化日期:按指定格式输出(如2024:05:20 10:00:00)
SELECT DATE_ADD(NOW(),INTERVAL 1 DAY); -- 日期加1天
SELECT DATE_ADD(NOW(),INTERVAL -1 YEAR); -- 日期减1年
SELECT DATE_SUB(NOW(),INTERVAL -1 DAY); -- 等价于DATE_ADD(NOW(),1 DAY)
SELECT DATEDIFF('2018-01-01 08:00','2019-01-01 10:00'); -- 日期差:结果-366(前者减后者)
SELECT TIME_TO_SEC('9:00')-TIME_TO_SEC('9:02'); -- 时间转秒数相减:结果-120(2分钟=120秒)