SQL上半部分

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秒)
相关推荐
Elastic 中国社区官方博客1 小时前
Elasticsearch:监控 LLM 推理和 Agent Builder 使用 OpenRouter
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
知识分享小能手1 小时前
Oracle 19c入门学习教程,从入门到精通,Oracle 数据表对象 —— 语法知识点详解与案例实践(10)
数据库·学习·oracle
Gobysec1 小时前
Goby 漏洞安全通告|GNU InetUtils Telnetd USER环境变量注入 权限绕过漏洞(CVE-2026-24061)
数据库·安全·gnu·漏洞分析·漏洞预警
wregjru1 小时前
【QT】2.QT 信号和槽
数据库
历程里程碑1 小时前
Linux 2 指令(2)进阶:内置与外置命令解析
linux·运维·服务器·c语言·开发语言·数据结构·ubuntu
麦兜*1 小时前
SpringBoot Profile多环境配置详解,一套配置应对所有场景
java·数据库·spring boot
天荒地老笑话么1 小时前
Linux 里 chmod 755 file.txt 是什么意思(权限配置)
linux·运维·服务器·网络安全
javajingling1 小时前
redis命令
数据库·redis·缓存
venus602 小时前
服务器里面多个网口,电口,光口如何区分使用
运维·服务器