SQL 语言基础

SQL(Structured Query Language)是关系型数据库的通用语言,按功能可分为四类:

类别全称代表语句
DDL数据定义CREATEALTERDROPTRUNCATE
DML数据操纵INSERTUPDATEDELETE
DQL数据查询SELECT
DCL数据控制GRANTREVOKE

下面以一张订单表为例,贯穿讲解查询的各个构件。

CREATE TABLE users (
    id    BIGINT PRIMARY KEY,
    name  VARCHAR(64)  NOT NULL,
    city  VARCHAR(64),
    vip   BOOLEAN      NOT NULL DEFAULT FALSE
);

CREATE TABLE orders (
    id         BIGINT PRIMARY KEY,
    user_id    BIGINT        NOT NULL,
    amount     DECIMAL(12,2) NOT NULL,
    status     VARCHAR(16)   NOT NULL,
    created_at DATE          NOT NULL
);

增删改

INSERT INTO users (id, name, city, vip)
VALUES (1, 'Alice', '北京', TRUE),
       (2, 'Bob',   '上海', FALSE);

UPDATE users SET city = '深圳' WHERE id = 2;

DELETE FROM orders WHERE status = 'cancelled';
Warning

UPDATEDELETE 一定要带 WHERE。误操作前可先用 SELECT 验证条件命中行数,并在事务中执行以便回滚。

查询:SELECT 的求值顺序

SQL 书写顺序与逻辑执行顺序并不一致,理解后者是写好查询的关键:

书写顺序:  SELECT → FROM → JOIN → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
执行顺序:  FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

这解释了为什么 WHERE 中不能使用 SELECT 定义的别名,而 ORDER BY 可以。

连接(JOIN)

-- 内连接:只保留两表都匹配的行
SELECT u.name, o.amount
FROM orders o
JOIN users u ON u.id = o.user_id;

-- 左外连接:保留左表全部行,右表缺失处为 NULL
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.name;
连接类型结果
INNER JOIN两表都匹配的行
LEFT JOIN左表全部 + 右表匹配
RIGHT JOIN右表全部 + 左表匹配
FULL JOIN两表全部(MySQL 需用 UNION 模拟)
CROSS JOIN笛卡尔积

聚合与分组

SELECT city,
       COUNT(*)          AS users,
       AVG(amount)       AS avg_amount,
       SUM(amount)       AS gmv
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY city
HAVING SUM(amount) > 10000
ORDER BY gmv DESC
LIMIT 10;

WHERE 过滤分组前的行,HAVING 过滤分组后的聚合结果,二者不可混用。

子查询与 CTE

-- 标量子查询
SELECT name
FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);

-- 公用表表达式(CTE),可读性优于嵌套子查询
WITH city_gmv AS (
    SELECT u.city, SUM(o.amount) AS gmv
    FROM orders o JOIN users u ON u.id = o.user_id
    GROUP BY u.city
)
SELECT * FROM city_gmv WHERE gmv > 10000;

窗口函数

窗口函数在不折叠行的前提下做组内计算,常用于排名、累计与同环比:

SELECT name,
       city,
       amount,
       ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS rn,
       SUM(amount)  OVER (PARTITION BY city)                      AS city_total
FROM orders o
JOIN users u ON u.id = o.user_id;
Tip

窗口函数的关键在于 OVER (PARTITION BY ... ORDER BY ...)PARTITION BY 划分窗口,ORDER BY 定义窗口内顺序。它与 GROUP BY 最大的区别是不减少结果行数

参数化查询

无论使用哪种语言访问数据库,都应使用参数占位符,交由驱动做安全转义:

rows, err := db.Query(
    "SELECT id, name FROM users WHERE city = ? AND age > ?",
    city, age,
)
Warning

严禁用字符串拼接构造 SQL(如 fmt.Sprintf("... WHERE name = '%s'", name)),这会引入 SQL 注入漏洞。占位符在 MySQL 中为 ?,在 PostgreSQL 中为 $1$2

小结

  • 记住 SELECT 的逻辑执行顺序,能解释大多数「为什么别名不能用」的问题。
  • 连接会放大行数,聚合前想清楚粒度(一行代表什么)。
  • WHEREHAVING 分工明确;CTE 与窗口函数让复杂查询保持可读。
  • 一律使用参数化查询,杜绝 SQL 注入。