小白python入门 - 26. 增删改查与过滤

1. 增删改查是什么?

上一课会了建表(定表头、主键、外键)。本课改的是:表里已经有的那些格子里的数据

业界常把四件事合称 CRUD

英文中文SQL 动词人话
CreateINSERT往表里加一行
ReadSELECT按条件把行找出来
UpdateUPDATE改已有行的某些列
DeleteDELETE删掉符合条件的行

继续用 Day25 的两张表想问题:

users(人)

idnameemail
1alicealice@example.com
2bobbob@example.com

ledger(账)

iduser_idamountnote
1112.5lunch
2230.0taxi
  • 增:再插入一笔账
  • 查:找出金额 ≥ 10 的账
  • 改:把 lunch 改成 15 元
  • 删:删掉某一笔 snack

本课工具仍是 SQLite + Python sqlite3。多表 JOIN、分组汇总留给下一课;这里把单表的增删改查和 WHERE 过滤 练熟。


2. 插入 INSERT(增)

2.1 最常用写法
INSERT INTO 表名 (1,2, ...) VALUES (1,2, ...);

例子:给 users 加一个用户 carol。

INSERT INTO users (name, email) VALUES ('carol', 'carol@example.com');

修改前(users 只有 2 人):

idnameemail
1alicealice@example.com
2bobbob@example.com

修改后(多了一行,id 自动变成 3):

idnameemail
1alicealice@example.com
2bobbob@example.com
3carolcarol@example.com

再给 carol 记一笔账:

INSERT INTO ledger (user_id, amount, note) VALUES (3, 8.0, 'coffee');

修改前(ledger 最后两行示意):

iduser_idamountnote
1112.5lunch
2230.0taxi

修改后(末尾多一行):

iduser_idamountnote
1112.5lunch
2230.0taxi
338.0coffee
  • 没写 id:若列是 INTEGER PRIMARY KEY AUTOINCREMENT,数据库会自动给 3、4、5…
  • 列顺序要和 VALUES 一一对应。
2.2 用 Python 时务必参数绑定
conn.execute(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    ("carol", "carol@example.com"),
)

? 是占位符,值放在后面的元组里。不要用 f-string 把用户输入拼进 SQL(防注入,Day24 已提过)。

2.3 插错了会怎样?(实例)
操作可能结果
name 不填却声明了 NOT NULL报错,插不进去
邮箱与别人重复(UNIQUE报错
ledger.user_id = 999 但用户表没有 999(开了外键)FOREIGN KEY constraint failed

记住:约束是在保护数据,不是故意刁难你。表内容在报错后保持修改前的样子(该次插入不生效)。

3. 查询 SELECT 入门(查 · 基础)

最小句型:

SELECT1,2 FROM 表名;
写法意思例子
SELECT * FROM users所有列都要调试时方便,业务里更推荐写清列名
SELECT name, email FROM users只要姓名和邮箱少传无用数据
SELECT name AS 姓名 FROM users给列起个别名结果里表头变成「姓名」(可选)

人话:SELECT 决定看哪些列FROM 决定从哪张表拿


4. 过滤 WHERE(查 · 核心)

只想看「符合条件的行」时,加 WHERE

SELECT... FROM 表名 WHERE 条件;
4.1 比较
运算符含义例子
=等于user_id = 1
!=<>不等于note != 'taxi'
> < >= <=大小比较amount >= 10
SELECT id, amount, note FROM ledger WHERE amount >= 10;

表中全部流水(查询前,库里有这些行):

iduser_idamountnote
1112.5lunch
2230.0taxi
31-5.0refund
4180.0book
529.9snack

WHERE amount >= 10 查出来(查询后你「看到」的结果):

idamountnote
112.5lunch
230.0taxi
480.0book

注意:SELECT 不会改表,表里仍是 5 行;只是结果集变少了。snack(9.9)被滤掉。

4.2 并且 / 或者
写法含义
AND两边都要成立
OR一边成立即可
NOT取反
SELECT id, amount, note FROM ledger
WHERE user_id = 1 AND amount > 0;

同一张 5 行流水里,这条语句「看到」的结果:

idamountnote
112.5lunch
480.0book

alice 的 refund(-5.0)因 amount > 0 被排除;bob 的账因 user_id = 1 被排除。

4.3 范围与列表
SELECT * FROM ledger WHERE amount BETWEEN 10 AND 50;
SELECT * FROM ledger WHERE user_id IN (1, 2);

BETWEEN 10 AND 50 结果示例(在上述 5 行上):

idamountnote
112.5lunch
230.0taxi

80 太大、9.9 太小、-5 不在范围内,都不会出现。

4.4 模糊匹配 LIKE
通配符含义例子
%任意多字符note LIKE '%a%' → 备注里含字母 a
_恰好一个字符name LIKE '_ob' → 三字母且后两字是 ob
SELECT id, note FROM ledger WHERE note LIKE '%a%';

结果示例:

idnote
2taxi
5snack

lunchrefundbook 里没有字母 a,不会出现。

4.5 空值:只能用 IS NULL

假设某行备注还没填:

idnote
6NULL(空)
7taxi
SELECT * FROM ledger WHERE note IS NULL;      -- 只能查到 id=6
SELECT * FROM ledger WHERE note IS NOT NULL;  -- 查到有字的行
-- 错误:WHERE note = NULL  (筛不出空备注)

原因:在 SQL 里,NULL 表示「不知道」,任何值与 NULL= 比较结果都不是「真」。

5. 排序与条数 ORDER BY / LIMIT

SELECT id, amount, note FROM ledger ORDER BY amount DESC;
关键字含义
ASC升序(从小到大,默认常是升序)
DESC降序(从大到小)
SELECT id, amount, note FROM ledger
ORDER BY amount DESC
LIMIT 3;

表中金额乱序时「全部行」仍是 5 笔;查询结果变成金额最高的 3 笔:

idamountnote
480.0book
230.0taxi
112.5lunch
SELECT id, amount, note FROM ledger
ORDER BY id
LIMIT 2 OFFSET 2;

按 id 排好后:第 1、2 行跳过,取第 3、4 行:

idamountnote
3-5.0refund
480.0book
子句人话
LIMIT n最多返回 n 行
OFFSET m先跳过 m 行再开始取

6. 修改 UPDATE(改)

UPDATE 表名 SET1 =1,2 =2 WHERE 条件;
6.1 改一个人的邮箱
UPDATE users SET email = 'alice.new@example.com' WHERE name = 'alice';

修改前:

idnameemail
1alicealice@example.com
2bobbob@example.com

修改后(只有 alice 那一行变了,bob 不动):

idnameemail
1alicealice.new@example.com
2bobbob@example.com
6.2 改一笔账的金额和备注
UPDATE ledger SET amount = 15.0, note = 'lunch-fixed' WHERE id = 1;

修改前:

iduser_idamountnote
1112.5lunch
2230.0taxi

修改后(只动 id=1):

iduser_idamountnote
1115.0lunch-fixed
2230.0taxi
安全铁律:UPDATE 几乎总要带 WHERE

若误写成:

UPDATE ledger SET amount = 0;   -- 没有 WHERE!

修改前:

idamount
115.0
230.0
480.0

修改后(灾难:每一行都变成 0):

idamount
10
20
40

所以:改之前可用同样 WHERE 先 SELECT,确认会影响哪些行,再 UPDATE


7. 删除 DELETE(删)

DELETE FROM 表名 WHERE 条件;
7.1 删掉一笔流水
DELETE FROM ledger WHERE id = 5;

修改前(含 snack):

iduser_idamountnote
1115.0lunch-fixed
2230.0taxi
31-5.0refund
4180.0book
529.9snack

修改后(id=5 整行消失,表还在):

iduser_idamountnote
1115.0lunch-fixed
2230.0taxi
31-5.0refund
4180.0book
7.2 DELETE 和 DROP TABLE 不是一回事
语句删的是什么修改后
DELETE FROM ledger WHERE id = 5一行数据表还在,少一行
DELETE FROM ledger;(无 WHERE)所有行表还在,0 行
DROP TABLE ledger整张表表名都不存在了
7.3 有外键时,不能随便删「还被记账的人」

bob 在流水里还有 taxi 时:

DELETE FROM users WHERE name = 'bob';

修改前: users 里有 bob;ledger 里还有 user_id=2
修改后: 往往两边都不变,并报错 FOREIGN KEY constraint failed(开了外键时)。
要先处理/删掉 bob 的流水,再删用户。


8. 事务小彩蛋(接 Day24)

  • conn.commit() → 确认写入
  • 出错或 conn.rollback() → 撤销未提交修改
  • 好习惯:先 SELECT 确认 → 再 UPDATE/DELETE → 再 commit

小例子:改错了还没 commit

时刻你在程序里做的磁盘上 ledger 里 id=1 的 amount
开始12.5
UPDATE ... amount=999(未 commit)内存里像改了多数情况下别人再开连接仍看到 12.5
conn.rollback()放弃修改仍是 12.5
conn.commit()确认变成 999

人话:没 commit 的改动,可以当成「还没盖章」,一 rollback 就回到修改前。


9. 实践:一条龙走完 CRUD

9.0 运行环境说明
项目要求
Python3.10+(推荐 3.12);标准库 sqlite3
工作目录自拟,下文以 ~/python-lab/src/day26 为例
ShellBash 兼容(mkdircat <<'EOF'

各系统建议:

系统建议
Linux / macOS系统终端 + python3
Windows推荐 Cygwin(或 WSL)
mkdir -p ~/python-lab/src/day26
cd ~/python-lab/src/day26
python3 --version
9.1 一键生成并运行
cat > crud_demo.py << 'EOF'
# Day26: CRUD + WHERE / ORDER / LIMIT
import sqlite3
from pathlib import Path

DB = Path(__file__).with_name("shop.db")


def show(title, rows):
    print(f"--- {title} ---")
    for row in rows:
        print(row)


def main():
    if DB.exists():
        DB.unlink()
    conn = sqlite3.connect(DB)
    conn.execute("PRAGMA foreign_keys = ON")

    conn.executescript(
        """
        CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT NOT NULL UNIQUE
        );
        CREATE TABLE ledger (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            amount REAL NOT NULL,
            note TEXT,
            FOREIGN KEY (user_id) REFERENCES users(id)
        );
        """
    )

    conn.execute(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        ("alice", "alice@example.com"),
    )
    conn.execute(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        ("bob", "bob@example.com"),
    )
    for row in [
        (1, 12.5, "lunch"),
        (2, 30.0, "taxi"),
        (1, -5.0, "refund"),
        (1, 80.0, "book"),
        (2, 9.9, "snack"),
    ]:
        conn.execute(
            "INSERT INTO ledger (user_id, amount, note) VALUES (?, ?, ?)",
            row,
        )
    conn.commit()
    show(
        "after insert: all ledger",
        conn.execute(
            "SELECT id, user_id, amount, note FROM ledger ORDER BY id"
        ).fetchall(),
    )

    show(
        "WHERE amount >= 10",
        conn.execute(
            "SELECT id, user_id, amount, note FROM ledger WHERE amount >= ?",
            (10,),
        ).fetchall(),
    )
    show(
        "WHERE user_id = 1 AND amount > 0",
        conn.execute(
            "SELECT id, amount, note FROM ledger "
            "WHERE user_id = ? AND amount > ?",
            (1, 0),
        ).fetchall(),
    )
    show(
        "WHERE note LIKE '%a%'",
        conn.execute(
            "SELECT id, note FROM ledger WHERE note LIKE ?",
            ("%a%",),
        ).fetchall(),
    )

    show(
        "ORDER BY amount DESC LIMIT 3",
        conn.execute(
            "SELECT id, amount, note FROM ledger "
            "ORDER BY amount DESC LIMIT 3"
        ).fetchall(),
    )
    show(
        "LIMIT 2 OFFSET 2",
        conn.execute(
            "SELECT id, amount, note FROM ledger "
            "ORDER BY id LIMIT 2 OFFSET 2"
        ).fetchall(),
    )

    conn.execute(
        "UPDATE users SET email = ? WHERE name = ?",
        ("alice.new@example.com", "alice"),
    )
    conn.execute(
        "UPDATE ledger SET amount = ?, note = ? WHERE id = ?",
        (15.0, "lunch-fixed", 1),
    )
    conn.commit()
    show(
        "after update: alice + ledger id=1",
        conn.execute(
            "SELECT id, name, email FROM users WHERE name = ?",
            ("alice",),
        ).fetchall()
        + conn.execute(
            "SELECT id, amount, note FROM ledger WHERE id = ?",
            (1,),
        ).fetchall(),
    )

    conn.execute("DELETE FROM ledger WHERE id = ?", (5,))
    conn.commit()
    show(
        "after delete id=5: all ledger",
        conn.execute(
            "SELECT id, user_id, amount, note FROM ledger ORDER BY id"
        ).fetchall(),
    )

    print("--- delete bob while ledger exists ---")
    try:
        conn.execute("DELETE FROM users WHERE name = ?", ("bob",))
        conn.commit()
        print("unexpected success")
    except sqlite3.IntegrityError as e:
        print("IntegrityError:", e)

    conn.close()


if __name__ == "__main__":
    main()
EOF

python3 crud_demo.py
9.2 实测输出
--- after insert: all ledger ---
(1, 1, 12.5, 'lunch')
(2, 2, 30.0, 'taxi')
(3, 1, -5.0, 'refund')
(4, 1, 80.0, 'book')
(5, 2, 9.9, 'snack')
--- WHERE amount >= 10 ---
(1, 1, 12.5, 'lunch')
(2, 2, 30.0, 'taxi')
(4, 1, 80.0, 'book')
--- WHERE user_id = 1 AND amount > 0 ---
(1, 12.5, 'lunch')
(4, 80.0, 'book')
--- WHERE note LIKE '%a%' ---
(2, 'taxi')
(5, 'snack')
--- ORDER BY amount DESC LIMIT 3 ---
(4, 80.0, 'book')
(2, 30.0, 'taxi')
(1, 12.5, 'lunch')
--- LIMIT 2 OFFSET 2 ---
(3, -5.0, 'refund')
(4, 80.0, 'book')
--- after update: alice + ledger id=1 ---
(1, 'alice', 'alice.new@example.com')
(1, 15.0, 'lunch-fixed')
--- after delete id=5: all ledger ---
(1, 1, 15.0, 'lunch-fixed')
(2, 2, 30.0, 'taxi')
(3, 1, -5.0, 'refund')
(4, 1, 80.0, 'book')
--- delete bob while ledger exists ---
IntegrityError: FOREIGN KEY constraint failed

总结

  • CRUD = INSERT / SELECT / UPDATE / DELETE
  • WHERE 决定动哪些行;改、删时漏写 WHERE 等于动整表,非常危险。
  • 过滤常用:比较、AND/ORIN/BETWEENLIKEIS NULL
  • ORDER BY 排序,LIMIT / OFFSET 控制条数与分页直觉。
  • DELETE 删行,DROP TABLE 删表;有外键时可能无法直接删「还被引用」的父行。
  • Python 里改完要 commit;值用 ? 绑定,不要拼 SQL。
  • 下一课:连接、聚合与子查询

小练笔

题 1

执行 UPDATE ledger SET amount = 0;(没有 WHERE)会怎样?

A. 只改 id 最小的一行
B. 所有行的 amount 都变成 0
C. 语法错误,不会执行
D. 只改主键为 0 的行

题 2

写出 SQL:从 ledger 中查出 金额大于等于 10id, amount, note

题 3

判断:WHERE note = NULL 可以正确找出备注为空的行。

(对 / 错)

题 4

简答:DELETE FROM users WHERE id = 1DROP TABLE users 有什么区别?

题 5(可选实践)

在本课 day26 目录、已跑过 crud_demo.pyshop.db 上:
再插入用户 carol,给她加一笔金额 8.0、备注 coffee,然后查出 name = 'carol' 的用户行。写出命令与关键输出。


小练笔参考答案

题 1

B
没有 WHERE 的 UPDATE 会更新表中所有行

题 2

SELECT id, amount, note FROM ledger WHERE amount >= 10;

题 3


应使用 WHERE note IS NULL(或 IS NOT NULL)。

题 4

  • DELETE ... WHERE:删掉符合条件的数据行,表结构还在。
  • DROP TABLE:删掉整张表(结构和所有数据)。

题 5(示例)

day26 目录:

python3 - <<'PY'
import sqlite3
conn = sqlite3.connect('shop.db')
conn.execute('PRAGMA foreign_keys = ON')
conn.execute(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ('carol', 'carol@example.com'),
)
uid = conn.execute(
    "SELECT id FROM users WHERE name = ?", ('carol',)
).fetchone()[0]
conn.execute(
    'INSERT INTO ledger (user_id, amount, note) VALUES (?, ?, ?)',
    (uid, 8.0, 'coffee'),
)
conn.commit()
print(conn.execute(
    'SELECT id, name, email FROM users WHERE name = ?', ('carol',)
).fetchall())
print(conn.execute(
    'SELECT id, user_id, amount, note FROM ledger WHERE user_id = ?',
    (uid,),
).fetchall())
conn.close()
PY

在「刚跑完本课 demo、未删库」时,用户行类似:

[(3, 'carol', 'carol@example.com')]

流水会多出一行 user_id=3, amount=8.0, note='coffee'(具体 id 以你库中为准)。

更多推荐