cv.cm

编码 · 编程语言

SQL 入门教程

阅读约 10 分钟

简短回答

SQL 是在 PostgreSQL、MySQL、SQLite 等关系型数据库中创建、查询和修改数据的标准语言。你编写 SELECT、INSERT、UPDATE、DELETE 等声明式语句,由数据库决定如何执行。本页讲解表设计、数据过滤、连接、分组、子查询、窗口函数和事务,并列出 PostgreSQL、MySQL 和 SQLite 的主要差异。

SQL 是什么,用来做什么

SQL(Structured Query Language,结构化查询语言)是操作关系型数据库的标准语言,数据以行和列组成的表格形式存储。你只需描述想要什么数据,由数据库引擎决定如何查找。SQL 由 ISO/IEC 9075 标准化,所有主流数据库都实现了标准的核心部分,并各有自己的扩展。

开发者、数据分析师、数据工程师和数据库管理员每天都在使用 SQL:存储应用数据、回答业务问题、生成报表以及为数据管道提供数据。最常见的开源引擎是 PostgreSQL、MySQL(及其分支 MariaDB)和 SQLite;商业数据库有 Microsoft SQL Server 和 Oracle Database。

准备数据库并运行第一条查询

最简单的入门方式是 SQLite,它是无需服务器、存放在单个文件中的数据库,macOS 和大多数 Linux 系统已预装。PostgreSQL 和 MySQL 以服务器方式运行,并自带命令行客户端 psql 和 mysql。

sqlite3 shop.db                      # SQLite: opens or creates a file
psql -U postgres -d shop             # PostgreSQL client
mysql -u root -p shop                # MySQL client
SELECT 'Hello, world!' AS greeting;
-- greeting
-- Hello, world!

SQL 关键字不区分大小写。关键字用大写、名称用小写是常见的约定,可以让查询更易读。语句以分号结尾,-- 开始一行注释。

建表:数据类型与约束

表定义列出各列、列的类型,以及保证数据有效的约束。本页的所有示例都使用 customers 和 orders 两张表。

CREATE TABLE customers (
  id    INTEGER PRIMARY KEY,
  name  VARCHAR(100) NOT NULL,
  city  VARCHAR(50)
);
CREATE TABLE orders (
  id          INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL REFERENCES customers(id),
  amount      DECIMAL(10, 2) NOT NULL,
  ordered_on  DATE NOT NULL
);
INSERT INTO customers (id, name, city) VALUES
  (1, 'Ann', 'Oslo'), (2, 'Bo', 'Rome'), (3, 'Cy', 'Oslo');
INSERT INTO orders (id, customer_id, amount, ordered_on) VALUES
  (1, 1, 30.00, '2026-01-05'),
  (2, 1, 45.50, '2026-02-10'),
  (3, 2, 12.00, '2026-02-11');
类型存储内容说明
INTEGER, BIGINT整数大 ID 请使用 BIGINT
DECIMAL(p, s)精确小数用于金额,切勿使用 FLOAT
VARCHAR(n), TEXT字符串TEXT 不在标准中,但被广泛支持
DATE, TIMESTAMP日期和时间字面量写成 '2026-01-05'
BOOLEAN真或假MySQL 将其视为 TINYINT(1)

约束包括 PRIMARY KEY(行的唯一标识)、NOT NULL、UNIQUE、CHECK (condition) 以及 FOREIGN KEY / REFERENCES(值必须存在于另一张表中)。字符串使用单引号;在标准 SQL 中,双引号用于标识符。

SELECT:过滤、排序与限制行数

SELECT 用于读取数据。WHERE 过滤行,ORDER BY 排序,LIMIT(或标准的 FETCH FIRST n ROWS ONLY)限制返回的行数。

SELECT name, city
FROM customers
WHERE city = 'Oslo'
ORDER BY name;
-- Ann | Oslo
-- Cy  | Oslo
SELECT id, amount,
       CASE WHEN amount >= 40 THEN 'large'
            WHEN amount >= 20 THEN 'medium'
            ELSE 'small' END AS size
FROM orders
WHERE amount BETWEEN 10 AND 50
ORDER BY amount DESC
LIMIT 2;
-- 2 | 45.50 | large
-- 1 | 30.00 | medium
SELECT name FROM customers WHERE city IS NULL;   -- not: city = NULL
SELECT name FROM customers WHERE name LIKE 'A%'; -- Ann

JOIN:连接多张表

连接(join)使用条件(通常是外键)匹配两张表的行。INNER JOIN(或直接写 JOIN)只保留匹配的行;LEFT JOIN 保留左表的所有行,并用 NULL 填充缺失的列。

SELECT c.name, o.amount
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.id
ORDER BY o.amount DESC;
-- Ann | 45.50
-- Ann | 30.00
-- Bo  | 12.00
SELECT c.name, o.id AS order_id
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
WHERE o.id IS NULL;        -- customers with no orders
-- Cy | NULL
连接类型返回结果
INNER JOIN两张表中都匹配的行
LEFT JOIN左表所有行,加上右表中匹配的行,没有匹配则为 NULL
RIGHT JOIN右表所有行,加上左表中匹配的行,没有匹配则为 NULL
FULL OUTER JOIN两侧的所有行;MySQL 不支持
CROSS JOIN行的所有组合(笛卡尔积)

GROUP BY 与聚合函数

COUNT、SUM、AVG、MIN、MAX 等聚合函数把多行汇总为一行。GROUP BY 让每个分组产生一行结果,HAVING 在聚合之后过滤分组,而 WHERE 在聚合之前过滤行。

SELECT c.name,
       COUNT(o.id)               AS orders,
       COALESCE(SUM(o.amount), 0) AS total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY total DESC;
-- Ann | 2 | 75.50
-- Bo  | 1 | 12.00
-- Cy  | 0 | 0
SELECT city, COUNT(*) AS n
FROM customers
GROUP BY city
HAVING COUNT(*) > 1;
-- Oslo | 2

COUNT(*) 统计行数,而 COUNT(column) 只统计非空值,这就是上面 Cy 的订单数为 0 的原因。COALESCE 返回第一个非空的参数。

子查询、CTE 与窗口函数

子查询是嵌套在另一个查询里的查询。公用表表达式(CTE)用 WITH 编写,可以给子查询命名,让冗长的查询更易读。ROW_NUMBER、RANK、SUM(...) OVER 等窗口函数在相关行之间计算数值,而不会把它们折叠成分组。

SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE amount > 20);
-- Ann
WITH totals AS (
  SELECT customer_id, SUM(amount) AS total
  FROM orders
  GROUP BY customer_id
)
SELECT c.name, t.total,
       RANK() OVER (ORDER BY t.total DESC) AS rnk
FROM totals AS t
JOIN customers AS c ON c.id = t.customer_id;
-- Ann | 75.50 | 1
-- Bo  | 12.00 | 2

PostgreSQL、SQLite 3.25 及以上版本、MySQL 8.0 及以上版本都支持 CTE 和窗口函数。

修改数据:INSERT、UPDATE、DELETE 与事务

UPDATE 和 DELETE 会影响所有满足 WHERE 子句的行。事务把多条语句组合在一起,要么全部成功,要么全部失败。

UPDATE customers SET city = 'Paris' WHERE id = 2;
DELETE FROM orders WHERE amount < 15;
BEGIN;
UPDATE orders SET amount = amount - 10 WHERE id = 1;
UPDATE orders SET amount = amount + 10 WHERE id = 2;
COMMIT;                    -- or ROLLBACK; to undo both
SELECT id, amount FROM orders;
-- 1 | 20.00
-- 2 | 55.50

PostgreSQL、MySQL 与 SQLite 的差异

SQL 的核心部分是可移植的,但各引擎在细节上有所不同。以下是初学者最先遇到的差异。

特性PostgreSQLMySQLSQLite
自增 IDGENERATED ALWAYS AS IDENTITY 或 SERIALAUTO_INCREMENTINTEGER PRIMARY KEY
名称加引号"name"名称两侧加反引号"name"
限制行数LIMIT n 或 FETCH FIRST n ROWS ONLYLIMIT nLIMIT n
字符串拼接a || bCONCAT(a, b)a || b
布尔值真正的 BOOLEAN 类型TINYINT(1)存储为 0 和 1
类型检查严格默认的严格模式下为严格灵活,除非表声明为 STRICT
Upsert(存在则更新)ON CONFLICT ... DO UPDATEON DUPLICATE KEY UPDATEON CONFLICT ... DO UPDATE

其他差异还包括日期函数(PostgreSQL 和 MySQL 用 NOW(),SQLite 用 datetime('now'))、字符串比较是否区分大小写,以及 MySQL 不支持的全外连接。

常见错误与后续学习

  • 通过拼接用户输入来构造查询,这会导致 SQL 注入。请始终使用编程语言提供的参数化查询(占位符,如 ? 或 $1)。
  • 在分组查询中选择既不在 GROUP BY 中、也不在聚合函数内的列。
  • 用 FLOAT 而不是 DECIMAL 存金额。
  • 忘记给连接和过滤中用到的列建索引;CREATE INDEX idx_orders_customer ON orders(customer_id); 和 EXPLAIN 对此很有帮助。
  • 以为不写 ORDER BY 时行会按固定顺序返回。

进一步学习时,请阅读官方手册:PostgreSQL 教程、MySQL 参考手册和 SQLite 的 SQL 语言参考。可以在真实数据集上练习:先用白话写出问题,再把每个问题转换成查询。

常见问题

SQL 是编程语言吗?

SQL 是声明式查询语言:你描述想要的结果,而不是计算步骤。PL/pgSQL、T-SQL 等厂商扩展增加了变量、循环和存储过程。

初学者应该从哪个数据库开始?

SQLite 不需要服务器,非常适合学习。PostgreSQL 适合学习符合标准的 SQL 并用于真实应用,MySQL 则常见于虚拟主机。

WHERE 和 HAVING 有什么区别?

WHERE 在分组之前过滤单独的行,HAVING 在聚合函数计算之后过滤分组。

INNER JOIN 和 LEFT JOIN 有什么区别?

INNER JOIN 只返回在两张表中都有匹配的行。LEFT JOIN 返回左表的所有行,没有匹配时右表的列用 NULL 填充。

如何防止 SQL 注入?

绝不要把用户输入直接插入 SQL 文本。使用参数化查询或预编译语句,让数据库分别接收查询语句和参数值。

全部编码教程