当你在应用中需要存储用户信息、记录交易数据、查询商品列表时,背后都离不开数据库。而与数据库对话的语言,就是SQL。无论是后端开发、数据分析还是运维管理,SQL都是一项必备技能。理解SQL,是理解数据如何存储、查询和管理的起点。

SQL是什么

SQL是Structured Query Language的缩写,中文译为结构化查询语言。它是一种专门用于管理关系型数据库的编程语言,可以用来查询、插入、更新和删除数据,也可以用来创建和修改数据库结构。

SQL的诞生可以追溯到1970年。那年,IBM研究员Edgar F. Codd发表了一篇里程碑式的论文《A Relational Model of Data for Large Shared Data Banks》,提出了关系模型的概念。这篇论文奠定了现代关系型数据库的理论基础。1974年,Donald Chamberlin和Raymond Boyce基于Codd的关系模型,设计了SEQUEL(Structured English Query Language),这就是SQL的前身。

1986年,美国国家标准学会(ANSI)将SQL定为标准,随后国际标准化组织(ISO)也于1987年采纳。自此,SQL成为关系型数据库的通用语言。从SQL-86到SQL-92,再到SQL:1999、SQL:2003,标准不断演进,加入了窗口函数、公用表表达式(CTE)、JSON支持等现代特性,但核心的查询语法保持稳定。

关系型数据库的基本概念

在学习SQL之前,需要先理解关系型数据库的几个基本概念。

表、行、列

关系型数据库用**表(Table)**来组织数据。表类似于Excel电子表格,由行和列组成:

  • 列(Column):也称为字段,定义了数据的类型和含义。比如一个"用户"表可能有idnameemailcreated_at等列。
  • 行(Row):也称为记录,代表一条具体的数据。每一行是一条完整的记录,比如一个用户的信息。
┌────┬──────────┬─────────────────┬─────────────────────┐
│ id │   name   │      email      │     created_at      │
├────┼──────────┼─────────────────┼─────────────────────┤
│  1 │ 张三     │ [email protected]  │ 2026-01-15 10:30:00 │
│  2 │ 李四     │ [email protected]     │ 2026-02-20 14:22:00 │
│  3 │ 王五     │ [email protected]   │ 2026-03-01 09:15:00 │
└────┴──────────┴─────────────────┴─────────────────────┘

图片来源:基于关系型数据库标准模型绘制

主键与外键

**主键(Primary Key)**是表中唯一标识每一行的列或列组合。主键的值必须唯一且不能为空。在上面的例子中,id列就是主键——每个用户都有唯一的ID,通过ID可以精确找到对应的用户。

**外键(Foreign Key)**用于建立表与表之间的关联。外键是一个表中的列,其值引用另一个表的主键。比如"订单"表中的user_id可以是外键,指向"用户"表的id,表示这个订单属于哪个用户。

数据类型

每一列都有特定的数据类型,决定了该列可以存储什么样的数据:

数据类型 说明 示例
INT 整数 1, 100, -50
VARCHAR(n) 可变长度字符串 ‘Hello’, ‘数据库’
TEXT 长文本 大段文章内容
DECIMAL(p,s) 精确小数 123.45
DATE 日期 ‘2026-03-08’
DATETIME 日期时间 ‘2026-03-08 10:30:00’
BOOLEAN 布尔值 TRUE, FALSE

不同数据库的数据类型名称可能略有差异,但概念相同。

SELECT语句:查询数据

SELECT是SQL中最常用的语句,用于从数据库中查询数据。

基本语法

SELECT 列名1, 列名2, ... FROM 表名;

查询所有列使用星号*

SELECT * FROM users;

查询特定列:

SELECT name, email FROM users;

使用别名

AS关键字为列或表指定临时名称,使结果更易读:

SELECT name AS 用户名, email AS 邮箱 FROM users;

DISTINCT去重

使用DISTINCT关键字去除重复值:

-- 查询所有不同的城市
SELECT DISTINCT city FROM users;

WHERE子句:条件过滤

WHERE子句用于指定查询条件,只返回满足条件的行。

比较运算符

-- 等于
SELECT * FROM users WHERE id = 1;

-- 不等于
SELECT * FROM users WHERE status != 'deleted';

-- 大于、小于
SELECT * FROM products WHERE price > 100;
SELECT * FROM orders WHERE created_at < '2026-01-01';

逻辑运算符

组合多个条件:

-- AND:两个条件都满足
SELECT * FROM users WHERE age >= 18 AND status = 'active';

-- OR:满足任一条件
SELECT * FROM users WHERE city = '北京' OR city = '上海';

-- NOT:取反
SELECT * FROM users WHERE NOT status = 'deleted';

BETWEEN和IN

BETWEEN用于范围查询(包含边界):

-- 价格在100到500之间的商品
SELECT * FROM products WHERE price BETWEEN 100 AND 500;

IN用于匹配列表中的值:

-- 来自北京、上海、广州的用户
SELECT * FROM users WHERE city IN ('北京', '上海', '广州');

LIKE模糊匹配

使用通配符进行模式匹配:

  • %匹配任意个字符
  • _匹配单个字符
-- 姓"张"的用户
SELECT * FROM users WHERE name LIKE '张%';

-- 邮箱以@test.com结尾
SELECT * FROM users WHERE email LIKE '%@test.com';

-- 名字第二个字是"小"
SELECT * FROM users WHERE name LIKE '_小%';

NULL值处理

NULL表示"无值"或"未知"。判断NULL不能使用等号,必须使用IS NULLIS NOT NULL

-- 查询没有填写邮箱的用户
SELECT * FROM users WHERE email IS NULL;

-- 查询已填写邮箱的用户
SELECT * FROM users WHERE email IS NOT NULL;

ORDER BY子句:排序结果

ORDER BY用于对查询结果排序。

-- 按创建时间升序排列(默认)
SELECT * FROM users ORDER BY created_at;

-- 按价格降序排列
SELECT * FROM products ORDER BY price DESC;

-- 多列排序:先按城市,再按姓名
SELECT * FROM users ORDER BY city ASC, name ASC;

ASC表示升序(默认),DESC表示降序。

LIMIT和OFFSET:分页查询

当数据量大时,通常需要分页显示。LIMIT限制返回的行数,OFFSET指定跳过的行数。

-- 只返回前10条记录
SELECT * FROM users LIMIT 10;

-- 跳过前20条,返回接下来的10条(第3页,每页10条)
SELECT * FROM users LIMIT 10 OFFSET 20;

不同数据库的分页语法略有不同:

  • MySQL/PostgreSQL:LIMIT n OFFSET m
  • SQL Server:OFFSET m ROWS FETCH NEXT n ROWS ONLY
  • Oracle:FETCH FIRST n ROWS ONLY

聚合函数

聚合函数对一组值进行计算,返回单个值。

常用聚合函数

函数 作用 示例
COUNT() 计数 COUNT(*) 统计行数
SUM() 求和 SUM(amount) 计算总额
AVG() 平均值 AVG(price) 计算平均价格
MAX() 最大值 MAX(score) 最高分
MIN() 最小值 MIN(price) 最低价格
-- 统计用户总数
SELECT COUNT(*) FROM users;

-- 计算订单总金额
SELECT SUM(amount) FROM orders;

-- 计算商品平均价格
SELECT AVG(price) FROM products;

-- 找出最高和最低价格
SELECT MAX(price) AS 最高价, MIN(price) AS 最低价 FROM products;

COUNT的特殊用法

-- 统计总行数
SELECT COUNT(*) FROM users;

-- 统计某列非NULL值的数量
SELECT COUNT(email) FROM users;

-- 统计不同值的数量
SELECT COUNT(DISTINCT city) FROM users;

GROUP BY和HAVING:分组统计

GROUP BY分组

GROUP BY将数据按某列的值分组,常与聚合函数配合使用。

-- 统计每个城市的用户数量
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;

-- 统计每个商品的销售额
SELECT product_id, SUM(quantity * price) AS total_sales
FROM order_items
GROUP BY product_id;

HAVING过滤分组

HAVING用于过滤分组后的结果,类似于WHERE,但作用于分组而不是行。

-- 找出用户数超过100的城市
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 100;

-- 找出销售额超过10000的商品
SELECT product_id, SUM(amount) AS total
FROM orders
GROUP BY product_id
HAVING SUM(amount) > 10000;

WHERE和HAVING的区别:

  • WHERE在分组前过滤行
  • HAVING在分组后过滤分组

SQL查询的执行顺序

理解SQL的执行顺序对于正确编写查询非常重要。SQL语句的书写顺序和执行顺序是不同的:

书写顺序

SELECT column
FROM table
WHERE condition
GROUP BY column
HAVING condition
ORDER BY column
LIMIT n;

执行顺序

  1. FROM(确定数据源)
  2. WHERE(过滤行)
  3. GROUP BY(分组)
  4. HAVING(过滤分组)
  5. SELECT(选择列)
  6. DISTINCT(去重)
  7. ORDER BY(排序)
  8. LIMIT/OFFSET(限制行数)

这意味着在WHERE子句中不能使用SELECT中定义的别名,因为SELECT在WHERE之后执行:

-- 错误:WHERE不能使用SELECT中的别名
SELECT name, age * 2 AS double_age
FROM users
WHERE double_age > 40;  -- 报错!

-- 正确:使用完整表达式
SELECT name, age * 2 AS double_age
FROM users
WHERE age * 2 > 40;

JOIN:多表关联

实际应用中,数据通常分散在多个表中。JOIN用于将多个表的数据关联起来。

假设有两个表:users(用户表)和orders(订单表)。

users表                    orders表
┌────┬──────────┐          ┌────┬─────────┬─────────┐
│ id │   name   │          │ id │ user_id │ amount  │
├────┼──────────┤          ├────┼─────────┼─────────┤
│  1 │ 张三     │          │  1 │    1    │  100.00 │
│  2 │ 李四     │          │  2 │    1    │  200.00 │
│  3 │ 王五     │          │  3 │    2    │  150.00 │
└────┴──────────┘          └────┴─────────┴─────────┘

图片来源:基于SQL JOIN标准语义绘制

INNER JOIN内连接

返回两个表中匹配的行:

SELECT users.name, orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

结果:

┌──────────┬─────────┐
│   name   │ amount  │
├──────────┼─────────┤
│ 张三     │  100.00 │
│ 张三     │  200.00 │
│ 李四     │  150.00 │
└──────────┴─────────┘

王五没有订单,所以不在结果中。

LEFT JOIN左连接

返回左表所有行,右表没有匹配则为NULL:

SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

结果:

┌──────────┬─────────┐
│   name   │ amount  │
├──────────┼─────────┤
│ 张三     │  100.00 │
│ 张三     │  200.00 │
│ 李四     │  150.00 │
│ 王五     │   NULL  │
└──────────┴─────────┘

王五没有订单,但仍然出现在结果中,amount为NULL。

RIGHT JOIN右连接

返回右表所有行,左表没有匹配则为NULL。实际使用较少,通常可以用LEFT JOIN替代。

FULL OUTER JOIN全连接

返回两个表所有行,没有匹配则为NULL。不是所有数据库都支持。

JOIN图解

使用Venn图可以直观理解各种JOIN:

INNER JOIN        LEFT JOIN         RIGHT JOIN        FULL JOIN
   (A ∩ B)          (A)               (B)              (A ∪ B)
┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐
│    A    │      │    A    │      │    A    │      │    A    │
│  ┌───┐  │      │  ┌───┐  │      │  ┌───┐  │      │  ┌───┐  │
│  │   │  │      │  │   │  │      │  │   │  │      │  │   │  │
│  └───┘  │      │  └───┘  │      │  └───┘  │      │  └───┘  │
│    B    │      │    B    │      │    B    │      │    B    │
└─────────┘      └─────────┘      └─────────┘      └─────────┘

图片来源:基于LearnSQL.com的SQL JOIN可视化教程绘制,参考 https://learnsql.com/blog/sql-joins/

INSERT:插入数据

INSERT用于向表中添加新数据。

插入单行

-- 指定列名和值
INSERT INTO users (name, email, city)
VALUES ('赵六', '[email protected]', '深圳');

-- 插入所有列(顺序必须与表结构一致)
INSERT INTO users
VALUES (4, '赵六', '[email protected]', '深圳', '2026-03-08 10:00:00');

插入多行

INSERT INTO users (name, email, city)
VALUES 
    ('赵六', '[email protected]', '深圳'),
    ('孙七', '[email protected]', '杭州'),
    ('周八', '[email protected]', '成都');

从查询结果插入

-- 将活跃用户复制到新表
INSERT INTO active_users (name, email)
SELECT name, email FROM users WHERE status = 'active';

UPDATE:更新数据

UPDATE用于修改表中的数据。

-- 更新单个字段
UPDATE users SET city = '广州' WHERE id = 1;

-- 更新多个字段
UPDATE users 
SET city = '广州', email = '[email protected]' 
WHERE id = 1;

-- 更新多条记录
UPDATE products SET price = price * 1.1 WHERE category = '电子';

重要提示:永远不要忘记WHERE子句,否则会更新整张表!

-- 危险!会更新所有用户的城市
UPDATE users SET city = '北京';

DELETE:删除数据

DELETE用于删除表中的数据。

-- 删除指定记录
DELETE FROM users WHERE id = 1;

-- 删除满足条件的记录
DELETE FROM users WHERE status = 'deleted';

-- 删除所有记录(保留表结构)
DELETE FROM users;

重要提示:永远不要忘记WHERE子句,否则会删除整张表的数据!

-- 危险!会删除所有用户
DELETE FROM users;

CREATE TABLE:创建表

CREATE TABLE用于创建新表。

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE,
    city VARCHAR(50) DEFAULT '北京',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

常用约束:

  • PRIMARY KEY:主键
  • NOT NULL:不能为空
  • UNIQUE:值必须唯一
  • DEFAULT:默认值
  • AUTO_INCREMENT:自动递增(MySQL语法)

创建带外键的表

CREATE TABLE orders (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    amount DECIMAL(10, 2),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

ALTER TABLE:修改表结构

ALTER TABLE用于修改已存在的表结构。

-- 添加列
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- 删除列
ALTER TABLE users DROP COLUMN phone;

-- 修改列类型
ALTER TABLE users MODIFY COLUMN name VARCHAR(100);

-- 添加约束
ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);

-- 删除约束
ALTER TABLE users DROP CONSTRAINT uk_email;

DROP TABLE:删除表

DROP TABLE删除整个表,包括结构和数据。

-- 删除表
DROP TABLE users;

-- 如果表存在才删除(避免报错)
DROP TABLE IF EXISTS users;

子查询

子查询是嵌套在另一个查询中的查询。

在WHERE中使用子查询

-- 查询有订单的用户
SELECT * FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

-- 查询金额大于平均订单金额的订单
SELECT * FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);

在FROM中使用子查询

-- 查询每个用户的订单数量
SELECT u.name, o.order_count
FROM users u
JOIN (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY user_id
) o ON u.id = o.user_id;

视图(VIEW)

视图是保存的查询,可以像表一样使用。

-- 创建视图
CREATE VIEW active_users AS
SELECT id, name, email FROM users WHERE status = 'active';

-- 查询视图
SELECT * FROM active_users;

-- 删除视图
DROP VIEW active_users;

视图的优点:

  • 简化复杂查询
  • 提供数据安全性(隐藏敏感列)
  • 保持数据一致性

SQL注释

良好的注释习惯让代码更易维护:

-- 单行注释

/* 
   多行注释
   可以跨越多行
*/

SELECT name,  -- 选择用户名
       email   -- 和邮箱
FROM users;

安全实践

防止SQL注入

SQL注入是最常见的安全漏洞之一。永远不要直接拼接用户输入到SQL语句中:

-- 危险!可能被注入
SELECT * FROM users WHERE name = '" + userName + "'";

使用参数化查询(Prepared Statement):

# Python示例
cursor.execute("SELECT * FROM users WHERE name = %s", (user_name,))
// Java示例
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
stmt.setString(1, userName);

参数化查询将SQL语句和数据分离,从根本上防止了SQL注入。

最小权限原则

数据库用户应该只拥有完成工作所需的最小权限:

-- 创建只读用户
GRANT SELECT ON database.* TO 'readonly'@'localhost';

-- 只允许访问特定表
GRANT SELECT, INSERT ON database.orders TO 'app_user'@'localhost';

实践建议

学习路径:从简单的SELECT开始,逐步掌握WHERE、ORDER BY,再学习聚合函数和GROUP BY,最后掌握JOIN和子查询。

调试技巧:复杂查询可以分步验证。先写FROM和WHERE,确认数据正确后再加GROUP BY和SELECT。

性能意识:WHERE过滤在分组前执行,尽量在WHERE中过滤数据,减少后续处理的数据量。

命名规范:表名用复数(users、orders),列名用单数(name、email),使用下划线分隔(created_at)。

代码风格:关键字大写,表名列名小写,复杂查询适当换行缩进。


参考资料: