SQL数据库学习入门:常用语句与练习方法

SQL数据库学习入门:常用语句与练习方法

SQL数据库学习入门:常用语句与练习方法

SQL 是操作关系型数据库的常用语言。无论是保存用户资料、查询订单,还是统计每天的销售额,很多应用都会用 SQL 与数据库交互。入门时不必先掌握某一种数据库的全部特性;更有效的做法是先理解表、行、列和关系,再用一个轻量数据库练习查询、修改、约束与事务。

本文以 SQLite 作为可运行示例。SQLite 通常不需要启动独立数据库服务,Python 自带 sqlite3 模块,适合本地学习和原型验证。示例中的基础 SQL 在其他关系型数据库中也大多适用,但数据类型、自动生成主键、日期处理、分页语法和部分函数可能不同。实际使用时,应查阅目标数据库的文档,不要假设不同产品完全兼容。

一、先建立几个基本概念

  • 数据库:用于组织和保存数据的软件或文件。
  • 表:按行和列组织的一类数据,例如 customers 表保存客户。
  • 行:一条记录,例如某位客户的资料。
  • 列:记录中的一个属性,例如客户姓名、邮箱和创建时间。
  • 主键:用于唯一标识一行的列或列组合。
  • 外键:用于表达表之间关系的列,通常引用另一张表的主键。

SQL 语句常按用途分组:查询数据使用 SELECT;新增、修改、删除行使用 INSERT、UPDATE、DELETE;创建或修改表结构使用 CREATE、ALTER、DROP。这是便于学习的分类,不同数据库对权限和事务的处理可能有所区别。

二、准备一组练习数据

下面的脚本适用于 SQLite。可以将其保存为 setup.sql,在支持 SQLite 的工具中运行;也可以稍后通过 Python 执行。它创建客户和订单两张表,并插入几条数据。每条订单通过 customer_id 指向客户。

PRAGMA foreign_keys = ON;

DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;

CREATE TABLE customers (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    city TEXT NOT NULL,
    created_at TEXT NOT NULL
);

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    amount NUMERIC NOT NULL CHECK (amount >= 0),
    status TEXT NOT NULL,
    ordered_at TEXT NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

INSERT INTO customers (name, email, city, created_at) VALUES
    ('林青', '[email protected]', '杭州', '2025-01-05'),
    ('周宁', '[email protected]', '上海', '2025-01-12'),
    ('陈一', NULL, '杭州', '2025-02-03');

INSERT INTO orders (customer_id, amount, status, ordered_at) VALUES
    (1, 128.50, 'paid', '2025-02-10'),
    (1, 39.90, 'pending', '2025-02-11'),
    (2, 260.00, 'paid', '2025-02-12');

NOT NULL 要求列不能保存 NULL;UNIQUE 限制值不能重复;CHECK 用于校验数据条件。SQLite 中,普通的 INTEGER PRIMARY KEY 会作为行标识符使用;其他数据库生成主键的写法不同。SQLite 的外键约束需要启用,示例中的 PRAGMA foreign_keys = ON 应在连接上执行。约束可以防止一部分无效数据进入数据库,但不能替代业务规则校验、权限控制或安全设计。

三、查询数据:SELECT、筛选与排序

最基本的查询包含要读取的列和数据来源:

SELECT name, city
FROM customers;

开发中尽量明确列名,不要长期依赖 SELECT *。星号适合快速检查数据,但表结构增加列后,结果也会变化;返回不需要的列还会增加传输和处理开销。

用 WHERE 筛选行,用 ORDER BY 排序,用 LIMIT 控制返回条数:

SELECT id, name, city
FROM customers
WHERE city = '杭州'
ORDER BY id DESC
LIMIT 10;

WHERE 可以使用比较运算符,如 =、<>、>、>=,也可以组合条件:

SELECT id, customer_id, amount, status
FROM orders
WHERE status = 'paid'
  AND amount >= 100
ORDER BY amount DESC, id ASC;

多列排序按书写顺序逐层生效:先按金额从高到低,金额相同再按编号从小到大。若不写 ORDER BY,数据库不保证结果按某个固定顺序返回。不要把某次测试中观察到的顺序当成稳定约定。

BETWEEN 通常表示包含两端的范围;IN 用于匹配多个候选值:

SELECT id, amount, ordered_at
FROM orders
WHERE amount BETWEEN 50 AND 200
  AND status IN ('paid', 'pending');

筛选空值不能写成 email = NULL,应使用 IS NULL 或 IS NOT NULL:

SELECT id, name
FROM customers
WHERE email IS NULL;

这是因为 SQL 使用三值逻辑:比较结果可能为真、假或未知。涉及 NULL 的普通等号比较通常得到未知,无法像普通布尔值那样匹配。类似地,使用 NOT IN 时,如果候选集合中包含 NULL,结果可能与直觉不同;处理可能含空值的数据时,先确认业务含义,必要时改用明确的空值过滤或 NOT EXISTS。

LIKE 用于模式匹配:% 匹配任意长度字符,_ 匹配一个字符。

SELECT name, email
FROM customers
WHERE email LIKE '%@example.com';

如果用户输入本意是普通文字,不能忽略其中的 % 和 _:它们会被解释为通配符。要按字面内容搜索,需要按照所用数据库的规则转义通配符,并使用 ESCAPE 等机制。通配符放在开头的模式,例如 LIKE '%abc',通常也不容易利用普通索引进行高效查找。

四、聚合与分组

聚合函数可以对多行计算结果。常见函数包括 COUNT、SUM、AVG、MIN 和 MAX。

SELECT
    status,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount,
    AVG(amount) AS average_amount
FROM orders
GROUP BY status
ORDER BY total_amount DESC;

GROUP BY 把相同状态的行归为一组。若要在分组后筛选,应使用 HAVING;筛选单行条件则用 WHERE。可以把查询的逻辑顺序粗略理解为先从表中取行、按 WHERE 过滤、分组、按 HAVING 过滤组,最后排序和限制结果。

SELECT customer_id, SUM(amount) AS total_amount
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) > 100
ORDER BY total_amount DESC;

常见错误是把聚合条件写到 WHERE 中,例如试图用 WHERE SUM(amount) > 100。聚合值是在分组后产生的,应使用 HAVING。此外,COUNT(*) 统计行数,COUNT(column) 只统计该列非 NULL 的行,两者含义不同。

五、关联多张表:JOIN

当所需信息分散在多张表中,可以通过关联查询合并结果。下面查询订单及其客户姓名:

SELECT
    o.id AS order_id,
    c.name AS customer_name,
    o.amount,
    o.status
FROM orders AS o
JOIN customers AS c
    ON o.customer_id = c.id
ORDER BY o.id;

JOIN 默认指内连接,只返回关联条件两侧都匹配的行。若要保留左侧表中没有匹配项的行,可以用 LEFT JOIN。例如找出所有客户及其订单,包括没有订单的客户:

SELECT
    c.id,
    c.name,
    o.id AS order_id,
    o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
ORDER BY c.id, o.id;

左连接中,没有匹配订单的客户仍会出现,但订单列为 NULL。有一个容易踩的边界:如果在 WHERE 中写 o.status = 'paid',这些无订单客户会被过滤掉,效果可能接近内连接。若要保留所有客户,同时只关联已付款订单,可以把条件放进连接条件:

SELECT c.name, o.id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.id
   AND o.status = 'paid';

关联条件不充分也会造成重复行。例如只按城市关联客户和订单,而不是按客户编号,可能把一位城市内客户的订单错误地组合到其他客户上。写 JOIN 前要确认两侧的键以及关系是“一对一”“一对多”还是“多对多”。

六、新增、修改与删除数据

新增数据时,建议明确列名,避免依赖表中列的顺序:

INSERT INTO customers (name, email, city, created_at)
VALUES ('王明', '[email protected]', '成都', '2025-03-01');

一次插入多行时,可以使用多个值组:

INSERT INTO customers (name, email, city, created_at)
VALUES
    ('赵可', '[email protected]', '苏州', '2025-03-02'),
    ('孙安', NULL, '南京', '2025-03-03');

修改数据使用 UPDATE。通常必须写清楚 WHERE,否则会更新表中的每一行:

UPDATE orders
SET status = 'paid'
WHERE id = 2;

执行重要更新前,可先用相同筛选条件运行 SELECT,检查命中的行。更新金额等关键字段时也要确认单位、精度和业务约束;货币数值若有严格精度要求,不宜依赖浮点类型的近似运算,应按数据库和业务规范选择合适的定点数或最小货币单位表示。

删除数据同样要谨慎:

DELETE FROM orders
WHERE id = 3;

遗漏 WHERE 会删除表内全部行;删除表结构则是另一种操作,例如 DROP TABLE orders,影响更大。对线上数据执行修改或删除前,应确认环境、筛选范围、备份与恢复方案,并遵循团队的审批流程。不要把“语句能执行”误认为“操作安全”。

七、建表、约束与索引

建表时不仅要能存数据,还要明确哪些值允许重复、哪些列不能为空,以及表之间如何关联。主键、唯一约束和外键可帮助数据库维护数据一致性。对于字段含义,使用清晰的列名和合适的数据类型;例如日期不要随意混入格式不一致的文本。

索引用于帮助数据库查找数据。以频繁按客户编号查订单为例,可以为关联列创建索引:

CREATE INDEX idx_orders_customer_id
ON orders (customer_id);

索引并非越多越好。索引会占用存储空间,并可能增加插入、更新和删除的维护成本。是否需要索引,取决于数据规模、查询条件、排序和执行计划;小表可能不需要额外索引。复合索引的列顺序也会影响可用场景,不能只凭列名推断性能。实际优化应在目标数据库上查看执行计划,并用有代表性的数据测量。

八、事务:把相关操作作为一组处理

事务适用于一组必须共同成功或共同失败的操作,例如账户转账。事务的概念通常由 ACID 特性描述,但具体隔离级别、锁行为和并发细节由数据库产品决定。

BEGIN;

UPDATE accounts
SET balance = balance - 100
WHERE id = 1;

UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

COMMIT;

如果中间发现错误,可以在提交前执行 ROLLBACK 取消当前事务中的更改。上面的片段假设存在名为 accounts 的表,并不属于前面的练习数据。真实转账还需要检查账户是否存在、余额是否足够,并妥善处理并发请求;仅把两条更新放进事务,并不能自动实现所有业务规则。数据库驱动的自动提交设置、事务开启方式也可能不同,应按驱动文档使用。

九、用参数化查询避免把数据拼进 SQL

应用程序接收用户输入时,不要通过字符串拼接构造查询。例如,直接把登录名拼进 SQL,可能让输入改变语句结构,造成 SQL 注入。应使用数据库驱动提供的参数绑定接口,让 SQL 结构与数据值分开传递。

下面是可运行的 Python 示例。将数据库文件保存为 learning.db,先运行前面的建表和插入脚本,再运行此代码:

import sqlite3

name = input("请输入客户姓名:")

with sqlite3.connect("learning.db") as conn:
    row = conn.execute(
        "SELECT id, name, city FROM customers WHERE name = ?",
        (name,)
    ).fetchone()

    if row is None:
        print("没有找到客户")
    else:
        print(row)

SQLite 的问号占位符对应的参数需要按位置传入;只有一个参数时仍要写成单元素元组 (name,)。其他驱动可能使用不同占位符风格,例如命名参数或其他标记,应以该驱动的接口为准。参数绑定适用于数据值,通常不能用来替代表名、列名、排序方向或 SQL 关键字。若需要动态选择列或排序方向,应从代码中预先定义的白名单映射中选择,而不是直接接受任意输入并拼接。

参数化查询是防止 SQL 注入的重要措施,但不是完整的安全方案。应用仍需正确管理数据库账号权限、保护连接凭据、校验业务输入,并谨慎处理错误信息。数据库账号应只拥有完成任务所需的权限;不要把敏感信息写入日志或公开给无权访问的人。

十、分页查询的边界

SQLite 支持 LIMIT 和 OFFSET,可用于小规模分页:

SELECT id, name
FROM customers
ORDER BY id
LIMIT 20 OFFSET 40;

这表示跳过前 40 行,再返回最多 20 行。分页必须指定稳定排序,否则不同请求间记录顺序可能变化。随着偏移量增大,数据库可能需要扫描并跳过大量行,性能会变差;数据持续写入时,偏移分页也可能出现跨页重复或遗漏。大数据量场景可以考虑基于上一页最后一条记录的“游标式分页”,例如按递增主键查询下一批。不同数据库的分页语法和优化方式并不相同。

十一、常见错误速查

  • 忘记写 WHERE:UPDATE 或 DELETE 可能作用于全表。执行前先用 SELECT 检查目标范围。
  • 把 NULL 当普通值比较:使用 IS NULL,不要写 = NULL。
  • 误以为结果天然有序:需要稳定顺序时明确写 ORDER BY。
  • 把聚合条件写在 WHERE:分组后的聚合筛选应使用 HAVING。
  • 连接键不正确:可能导致重复、遗漏或错误组合。先确认主外键关系和结果行数。
  • 用字符串拼接输入值:可能产生注入风险。使用驱动的参数化查询。
  • 以为参数能代替列名:参数绑定处理数据值,不负责动态替换 SQL 结构;动态结构要使用白名单。
  • 把约束当成全部业务校验:数据库约束能保护数据,但复杂业务仍需在应用和数据库设计中共同处理。
  • 把 SQLite 行为当作通用标准:自动编号、日期函数、分页、事务和类型规则都可能存在差异,部署前要在目标数据库验证。

十二、循序渐进的练习方法

学习 SQL 的关键不是记住尽可能多的关键字,而是反复把业务问题翻译成查询。可以按下面的顺序练习,并始终使用前面的客户和订单数据。

  1. 单表筛选:查找某个城市的客户;查找金额高于指定值的订单;分别练习 AND、OR、IN 和范围条件。
  2. 排序和限制:按金额降序找出前三笔订单;为排序加入唯一编号作为次级排序键,避免金额相同造成顺序不稳定。
  3. 聚合:统计订单总数、平均金额和已付款金额总和;比较 COUNT(*) 与 COUNT(email) 的结果。
  4. 分组:统计每位客户的订单数和消费总额,再只保留订单数达到指定数量的客户。
  5. 关联:显示客户姓名与订单金额;改用 LEFT JOIN,确认没有订单的客户仍出现在结果中。
  6. 修改数据:新增一位客户,将一笔待处理订单改为已付款;先查询命中行,再执行更新。
  7. 事务:在事务中修改两行数据,提交一次、回滚一次,观察结果差异。
  8. 参数绑定:用 Python 接收姓名并查询,试着输入含引号的字符串,确认它仍被当作数据值,而不是 SQL 语法。

每做一道题,都可以按照同一流程检查:明确结果应包含哪些列;确认数据来自哪些表;确定筛选条件和空值含义;检查关联关系是否会产生重复行;最后验证排序、结果数量和边界数据。遇到不确定的查询,先用小数据集逐步执行,不要一次写出很长的语句再猜错误原因。

可以把练习结果整理成一个小项目:建立表结构,插入测试数据,编写查询文件,再用脚本自动运行查询并比对预期结果。随着熟练度提高,再学习子查询、公共表表达式、窗口函数、视图和执行计划。它们能解决更复杂的问题,但应建立在基本查询、连接、聚合和事务都掌握之后。

结语

SQL 入门的核心是把数据模型与查询逻辑对应起来:先明确表之间的关系,再选择列、过滤记录、聚合或关联,最后检查结果是否符合预期。日常操作中,明确排序、正确处理 NULL、谨慎执行更新和删除、使用参数化查询,是比记住冷门语法更重要的习惯。掌握这些基础后,再依据实际数据库产品学习其类型、索引、事务与性能特性,才能把练习经验可靠地带入应用开发。

© 版权声明

相关文章