Python SQLite:更新数据 (`UPDATE` 子句) 深度解析
目录
- 引言:数据维护的核心——
UPDATE子句 - SQL
UPDATE语句基础- 2.1 语法结构
- 2.2
SET子句:指定要修改的列和新值 - 2.3
WHERE子句的重要性:精准更新 - 2.4 慎用:更新所有行
WHERE子句在UPDATE中的应用- 3.1 比较运算符 (
=,!=,<,>,<=,>=) - 3.2 逻辑运算符 (
AND,OR,NOT) - 3.3 范围运算符 (
BETWEEN) - 3.4 集合运算符 (
IN,NOT IN) - 3.5 模式匹配 (
LIKE,GLOB) - 3.6 空值判断 (
IS NULL,IS NOT NULL)
- 3.1 比较运算符 (
UPDATE与表达式和子查询- 4.1 基于当前值的更新 (自增/自减)
- 4.2 基于同一表中其他列的更新
- 4.3 基于子查询结果的更新 (关联更新)
- 外键约束与
UPDATE操作的联动- 4.1
PRAGMA foreign_keys = ON;:启用外键是前提 - 4.2
ON UPDATE CASCADE:级联更新 - 4.3
ON UPDATE SET NULL:设置为空 - 4.4
ON UPDATE RESTRICT/NO ACTION:限制更新 - 4.5
ON UPDATE SET DEFAULT(SQLite 不支持)
- 4.1
- 在 Python
sqlite3中执行UPDATE操作- 6.1 连接数据库与创建游标
- 6.2 参数绑定:防止 SQL 注入的关键
- 6.2.1 位置参数 (
?) - 6.2.2 命名参数 (
:name)
- 6.2.1 位置参数 (
- 6.3 执行更新与提交事务
- 6.4 获取受影响的行数 (
cursor.rowcount)
- 事务管理:
commit()与rollback()- 7.1 为什么数据修改操作需要事务
- 7.2 演示
commit() - 7.3 演示
rollback()
- 错误处理
- 8.1
sqlite3.OperationalError(语法错误、数据库锁定) - 8.2
sqlite3.IntegrityError(唯一约束、外键约束违规) - 8.3
sqlite3.InterfaceError(参数绑定错误)
- 8.1
- 最佳实践与注意事项
- 9.1 再次强调:永远使用
WHERE进行有条件的更新! - 9.2 再次强调:永远使用参数绑定!
- 始终启用外键约束
- 始终使用事务
- 先
SELECT验证,再UPDATE - 理解并规划外键的
ON UPDATE行为 - 使用
with语句管理资源 ALTER TABLE限制与常见变通方案 (背景信息)- 安全警告:避免动态 SQL 中的用户输入 (表名/列名)
- 9.1 再次强调:永远使用
- 综合代码示例
- 10.1 数据库结构和数据准备 (包含外键)
- 10.2 各种
UPDATE操作示例 - 10.3 事务与回滚示例
- 10.4 外键
ON UPDATE行为示例
- 总结
1. 引言:数据维护的核心——UPDATE 子句
在任何数据库驱动的应用程序中,数据很少是一成不变的。用户资料可能会更新,产品价格会调整,订单状态会改变。为了反映这些变化,SQL 提供了 UPDATE 子句。UPDATE 语句允许您修改数据库表中现有行的一个或多个列的值。
与 INSERT(添加新数据)和 DELETE(移除数据)一样,UPDATE 是数据管理的基本操作之一。正确地使用 UPDATE 语句对于维护数据准确性和应用程序的正常运行至关重要。然而,与 INSERT 类似,UPDATE 操作也需要谨慎,尤其是在没有 WHERE 子句的情况下,它可能导致意外的大规模数据修改。
本指南将深入探讨 UPDATE 语句的语法、与 WHERE 子句的结合、基于表达式和子查询的更新、外键约束对其行为的影响,以及如何在 Python 的 sqlite3 模块中安全高效地执行更新操作,并强调事务管理、错误处理和最佳实践。
2. SQL UPDATE 语句基础
2.1 语法结构
UPDATE 语句的基本语法如下:
UPDATE table_name
SET column1 = value1, column2 = value2, ...
[WHERE condition];
UPDATE table_name: 指定要更新的表。SET column1 = value1, column2 = value2, ...: 指定要更新的列及其新值。您可以更新一个或多个列,列名和新值之间用等号连接,多个列-值对之间用逗号分隔。WHERE condition: (可选) 一个布尔表达式,用于指定哪些行应该被更新。只有满足此条件的行才会被修改。
2.2 SET 子句:指定要修改的列和新值
SET 子句是 UPDATE 语句的核心,它定义了哪些列将被修改以及它们的新值是什么。
示例:
-- 将某个用户的邮箱更新为新值
SET email = 'new_email@example.com'
-- 同时更新多个列
SET price = 150.00, stock = 50
2.3 WHERE 子句的重要性:精准更新
WHERE 子句在 UPDATE 语句中至关重要。它允许您精确地指定应该更新哪些行。
- 如果您想更新特定的行(例如,特定 ID 的用户),
WHERE子句是必不可少的。
示例:
-- 更新 user_id 为 1 的用户的邮箱
UPDATE users SET email = 'user1_new@example.com' WHERE user_id = 1;
-- 将所有状态为 'pending' 的订单更新为 'processing'
UPDATE orders SET status = 'processing' WHERE status = 'pending';
2.4 慎用:更新所有行
与 DELETE 类似,如果 UPDATE 语句省略了 WHERE 子句,它将修改指定表中所有行的指定列。这通常不是您想要的结果,可能导致大规模的数据错误。
-- 警告:这将把 products 表中所有产品的价格都设置为 100.00!
UPDATE products SET price = 100.00;
在执行任何 UPDATE 语句之前,务必仔细检查 WHERE 子句,并确保它能准确地隔离出您想要修改的行。
3. WHERE 子句在 UPDATE 中的应用
UPDATE 语句中的 WHERE 子句支持与 SELECT 语句中相同的运算符和逻辑,以便您可以构建复杂的条件来精确指定要更新的行。
3.1 比较运算符 (=, !=, <, >, <=, >=)
UPDATE products SET price = price * 1.1 WHERE category = 'Electronics' AND price < 500; -- 电子产品中价格低于500的涨价10%
UPDATE users SET status = 'inactive' WHERE last_login_date < '2023-01-01'; -- 更新不活跃用户
3.2 逻辑运算符 (AND, OR, NOT)
UPDATE orders SET is_processed = TRUE WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31' AND is_processed = FALSE; -- 更新1月份未处理订单
3.3 范围运算符 (BETWEEN)
UPDATE employees SET bonus = 1000 WHERE salary BETWEEN 50000 AND 75000; -- 给特定薪资范围的员工发奖金
3.4 集合运算符 (IN, NOT IN)
UPDATE customers SET loyalty_level = 'Gold' WHERE customer_id IN (101, 105, 112); -- 提升特定客户的会员等级
3.5 模式匹配 (LIKE, GLOB)
UPDATE products SET description = 'Discontinued Item' WHERE name LIKE '%Old%'; -- 标记名称包含'Old'的产品为已停产
3.6 空值判断 (IS NULL, IS NOT NULL)
UPDATE users SET default_address_id = 1 WHERE default_address_id IS NULL; -- 为没有默认地址的用户设置默认地址
4. UPDATE 与表达式和子查询
SET 子句中的新值不一定是一个常量,它也可以是基于现有列的表达式或子查询的结果。
4.1 基于当前值的更新 (自增/自减)
修改列的值,使其基于该列的当前值。
-- 将所有产品的库存减少 10
UPDATE products SET stock = stock - 10 WHERE stock >= 10;
-- 将所有员工的薪水增加 5%
UPDATE employees SET salary = salary * 1.05;
4.2 基于同一表中其他列的更新
一个列的值可以根据同一行中其他列的值进行更新。
-- 更新订单的总金额,使其等于数量乘以价格
UPDATE order_items SET total_price = quantity * unit_price WHERE total_price IS NULL;
4.3 基于子查询结果的更新 (关联更新)
通过子查询从另一个表获取值来更新当前表。这在 SQLite 中通过在 SET 子句中使用子查询来实现。
示例:
假设我们想更新 orders 表中的 total_amount,使其等于 order_items 表中所有相关订单项的总和。
-- 更新 orders 表的 total_amount
UPDATE orders
SET total_amount = (
SELECT SUM(oi.quantity * oi.price_at_order)
FROM order_items AS oi
WHERE oi.order_id = orders.order_id
)
WHERE is_completed = FALSE; -- 仅更新未完成的订单
5. 外键约束与 UPDATE 操作的联动
外键约束不仅维护了数据的一致性,还定义了当父表中的主键(或被引用的唯一键)被更新时,子表中的关联记录如何响应的行为。
4.1 PRAGMA foreign_keys = ON;:启用外键是前提
在 SQLite 中,外键约束默认是禁用的。您需要在每个数据库连接开始时执行 PRAGMA foreign_keys = ON; 才能使其生效。否则,即使定义了外键,UPDATE 操作也不会触发任何关联行为。
4.2 ON UPDATE CASCADE:级联更新
当父表中的主键(或被引用的唯一键)被更新时,子表中所有引用该父表键的对应外键值也会被自动更新以匹配新值。这是一种非常方便的同步机制,但必须谨慎使用。
示例:
如果 orders 表的 customer_id 外键定义为 ON UPDATE CASCADE,当您更新 customers 表中某个客户的 customer_id(假设 customer_id 不是 AUTOINCREMENT 且可修改),该客户的所有订单的 customer_id 也会被自动更新。
-- 定义外键时
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON UPDATE CASCADE -- 客户ID更新时,其所有订单的customer_id也会更新
);
4.3 ON UPDATE SET NULL:设置为空
当父表中的主键(或被引用的唯一键)被更新时,子表中所有引用该父表键的外键值将被设置为 NULL。这要求外键列允许 NULL 值。
示例:
如果 employees 表的 dept_id 外键定义为 ON UPDATE SET NULL,当您更新 departments 表中某个部门的 dept_id(假设 dept_id 可修改),该部门的所有员工的 dept_id 字段将被设置为 NULL。
-- 定义外键时
CREATE TABLE employees (
emp_id INTEGER PRIMARY KEY,
dept_id INTEGER, -- 允许为 NULL
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
ON UPDATE SET NULL -- 部门ID更新时,员工的部门ID设为 NULL
);
4.4 ON UPDATE RESTRICT / NO ACTION:限制更新
RESTRICT: 如果子表中存在引用父表行的记录,将阻止父表行的主键(或被引用的唯一键)更新操作。NO ACTION: 与RESTRICT类似,也是阻止更新。在 SQLite 中,RESTRICT和NO ACTION的行为是相同的,即如果存在子引用,则不允许更新父表的主键,并会抛出sqlite3.IntegrityError。
示例:
如果 orders 表的 customer_id 外键定义为 ON UPDATE RESTRICT,当您尝试更新 customers 表中一个有订单的客户的 customer_id 时,操作将被阻止。
-- 定义外键时
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON UPDATE RESTRICT -- 如果客户有订单,则阻止更新客户ID
);
4.5 ON UPDATE SET DEFAULT (SQLite 不支持)
某些数据库系统支持 ON UPDATE SET DEFAULT,但在 SQLite 中不直接支持此选项。如果需要类似行为,通常需要通过触发器或应用程序逻辑来实现。
6. 在 Python sqlite3 中执行 UPDATE 操作
6.1 连接数据库与创建游标
import sqlite3
import os
db_name = 'update_example.db'
# 假设 db_name 和连接代码已存在,并已设置 foreign_keys = ON
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON;")
6.2 参数绑定:防止 SQL 注入的关键
与 INSERT 和 DELETE 类似,当 UPDATE 语句的 SET 子句中的新值或 WHERE 子句中的条件包含动态值时,必须使用参数绑定。这不仅可以防止 SQL 注入攻击,还能正确处理各种数据类型的转义。
6.2.1 位置参数 (?)
new_email = 'updated_alice@example.com'
target_user_id = 1
cursor.execute("UPDATE users SET email = ? WHERE user_id = ?", (new_email, target_user_id))
6.2.2 命名参数 (:name)
new_price = 1250.00
min_stock = 10
cursor.execute("UPDATE products SET price = :new_price_val WHERE stock > :min_stock_val",
{'new_price_val': new_price, 'min_stock_val': min_stock})
6.3 执行更新与提交事务
执行 UPDATE 语句后,更改默认情况下不会立即写入磁盘。您需要调用 conn.commit() 来保存更改。如果不想保存更改,可以调用 conn.rollback()。
# 示例:更新产品 ID 为 1 的价格和库存
product_id_to_update = 1
new_price_val = 1300.00
new_stock_val = 25
cursor.execute("UPDATE products SET price = ?, stock = ? WHERE product_id = ?",
(new_price_val, new_stock_val, product_id_to_update))
conn.commit() # 提交更改
print(f"产品 ID {product_id_to_update} 的信息已更新。")
6.4 获取受影响的行数 (cursor.rowcount)
在执行 UPDATE 语句后,cursor.rowcount 属性将返回受影响的行数。这对于验证更新操作是否按预期执行非常有用。
old_category = 'General'
new_category = 'Miscellaneous'
cursor.execute("UPDATE products SET category = ? WHERE category = ?", (new_category, old_category))
updated_count = cursor.rowcount
conn.commit()
print(f"更新了 {updated_count} 个从 '{old_category}' 到 '{new_category}' 的产品类别。")
7. 事务管理:commit() 与 rollback()
数据修改操作(INSERT, UPDATE, DELETE)应该始终在事务中进行。事务确保了操作的原子性、一致性、隔离性和持久性 (ACID)。
7.1 为什么数据修改操作需要事务
- 原子性: 要么所有操作都成功,要么所有操作都失败并回滚。避免部分更改。
- 一致性: 确保数据库从一个有效状态转换到另一个有效状态。
- 持久性: 提交的更改是永久的。
Python sqlite3 模块默认在 commit() 之前的所有操作都在一个隐式事务中。
7.2 演示 commit()
# conn 和 cursor 已创建并设置 foreign_keys = ON
try:
# 开始一个隐式事务
cursor.execute("UPDATE products SET stock = stock - 1 WHERE product_id = 1")
cursor.execute("UPDATE products SET stock = stock - 2 WHERE product_id = 2")
conn.commit() # 如果没有异常,提交所有更改
print("成功更新了产品库存。")
except sqlite3.Error as e:
print(f"更新失败: {e}")
conn.rollback() # 出现异常时回滚所有更改
print("事务已回滚。")
7.3 演示 rollback()
回滚用于撤销在一个事务中执行的所有更改,将数据库恢复到事务开始时的状态。
# conn 和 cursor 已创建并设置 foreign_keys = ON
product_id_to_test = 3
original_stock = 0
try:
# 查询原始库存
cursor.execute("SELECT stock FROM products WHERE product_id = ?", (product_id_to_test,))
original_stock = cursor.fetchone()[0]
print(f"\n产品 ID {product_id_to_test} 的原始库存: {original_stock}")
# 尝试更新库存,但随后回滚
print("尝试更新产品库存,然后回滚...")
cursor.execute("UPDATE products SET stock = stock + 10 WHERE product_id = ?", (product_id_to_test,))
# 决定回滚
conn.rollback()
print("事务已回滚,更新操作已撤销。")
# 验证数据是否未被更新
cursor.execute("SELECT stock FROM products WHERE product_id = ?", (product_id_to_test,))
current_stock = cursor.fetchone()[0]
print(f"回滚后,产品 ID {product_id_to_test} 的当前库存: {current_stock}")
if current_stock == original_stock:
print("回滚成功。")
else:
print("回滚失败!")
except sqlite3.Error as e:
print(f"发生错误: {e}")
conn.rollback()
print("事务已回滚。")
注意: with sqlite3.connect(...) as conn: 语句块会自动处理 commit() 或 rollback()。如果 with 块正常退出,则 commit();如果发生异常,则 rollback()。这是管理数据库连接和事务的推荐方式。
8. 错误处理
在使用 UPDATE 子句执行查询时,最常见的错误是 sqlite3.OperationalError 和 sqlite3.IntegrityError。
8.1 sqlite3.OperationalError (语法错误、数据库锁定)
这通常表示 SQL 语法错误、引用的表或列不存在、数据库文件被锁定或损坏等问题。
try:
# 尝试更新不存在的列
cursor.execute("UPDATE products SET non_existent_column = 100 WHERE product_id = 1")
conn.commit()
except sqlite3.OperationalError as e:
print(f"\n捕获到 OperationalError: {e}")
print("更新失败,请检查列名和 SQL 语法。")
conn.rollback()
8.2 sqlite3.IntegrityError (唯一约束、外键约束违规)
- 唯一约束: 当您尝试将一列更新为一个已经存在于其他行的唯一列中的值时。
- 外键约束: 当您尝试将外键列更新为一个在父表中不存在的值,并且外键约束设置为
ON UPDATE RESTRICT/NO ACTION或默认行为时。
# 假设 'products' 表的 'name' 列是 UNIQUE
try:
print("\n尝试更新产品名称为已存在的名称 (应失败 due to UNIQUE constraint)...")
existing_product_name = "Laptop" # 假设这是已存在的名称
product_id_to_update = 2 # 假设这是另一个产品ID
cursor.execute("UPDATE products SET name = ? WHERE product_id = ?", (existing_product_name, product_id_to_update))
conn.commit()
print(f"产品 ID {product_id_to_update} 的名称已更新。") # 这条不会执行
except sqlite3.IntegrityError as e:
print(f"捕获到 IntegrityError: {e}")
print("更新产品名称失败,因为违反了唯一性约束。事务已回滚。")
conn.rollback()
except sqlite3.OperationalError as e:
print(f"捕获到 OperationalError: {e}")
conn.rollback()
8.3 sqlite3.InterfaceError (参数绑定错误)
当传递给 execute() 方法的参数数量或类型与 SQL 语句中的占位符不匹配时,可能会引发此错误。
try:
# 预期 2 个参数,但只提供 1 个
cursor.execute("UPDATE products SET price = ? WHERE product_id = ?", (100.00,))
conn.commit()
except sqlite3.InterfaceError as e:
print(f"\n捕获到 InterfaceError: {e}")
print("参数绑定错误,请检查参数数量。")
conn.rollback()
9. 最佳实践与注意事项
9.1 再次强调:永远使用 WHERE 进行有条件的更新!
这是防止意外数据修改的黄金法则。在执行 UPDATE 语句之前,请仔细检查 WHERE 子句。
9.2 再次强调:永远使用参数绑定!
对于 SET 子句中的新值或 WHERE 子句中的任何动态值,必须使用参数绑定 (? 或 :name)。这不仅防止 SQL 注入,也简化了值类型转换和转义。
9.3 始终启用外键约束
在每个数据库连接开始时,执行 PRAGMA foreign_keys = ON;。这确保了您的数据完整性得到维护,并且 ON UPDATE 行为按预期工作。
9.4 始终使用事务
将 UPDATE 操作(以及其他数据修改操作)封装在事务中。使用 conn.commit() 来保存更改,或 conn.rollback() 来撤销更改。使用 with sqlite3.connect(...) as conn: 是管理事务和连接的推荐方式。
9.5 先 SELECT 验证,再 UPDATE
对于复杂的 UPDATE 操作,尤其是涉及 WHERE 子句时,可以先使用相同的 WHERE 条件执行一个 SELECT 语句,检查哪些行会被选中,以确保 UPDATE 目标是正确的。
9.6 理解并规划外键的 ON UPDATE 行为
在设计数据库模式时,仔细考虑每个外键的 ON UPDATE 行为。选择 CASCADE, SET NULL, RESTRICT 中的哪一个取决于您的业务逻辑和数据完整性需求。
9.7 使用 with 语句管理资源
with sqlite3.connect(...) as conn: 结构确保连接在块结束时自动关闭(或在异常时回滚)。
9.8 ALTER TABLE 限制与常见变通方案 (背景信息)
(这与 UPDATE 查询的直接使用无关,但作为 SQLite 模式管理的一部分值得再次提及)SQLite 对 ALTER TABLE 的支持相对有限。例如,它不能直接删除列,也不能直接修改列的数据类型或约束(除了少数例外)。如果需要进行这些更复杂的模式更改,常见的变通方法是:
- 创建具有所需新结构的新表。
- 将旧表中的数据复制到新表(只复制兼容的列)。
- 删除旧表。
- 将新表重命名为旧表的名称。
理解这些限制有助于规划您的数据库模式演进策略。
9.9 安全警告:避免动态 SQL 中的用户输入 (表名/列名)
如果您的应用程序允许用户指定要更新的表名或列名,切勿直接将用户输入拼接到 UPDATE 语句中。表名和列名不能通过参数绑定传递。直接拼接未经净化的用户输入到 SQL 语句中可能会导致严重的 SQL 注入漏洞。正确的做法是:
- 维护一个允许更新的表名和列名“白名单”。
- 接收用户输入后,检查输入是否在白名单中。
- 如果合法,则使用
f-string或字符串格式化(仅限于白名单验证后的安全输入)来构建 SQL 语句。
10. 综合代码示例
此示例将构建一个简单的电商数据库模型,并演示各种 UPDATE 操作,包括使用 WHERE 子句、基于表达式的更新、触发外键级联更新、遇到唯一性约束错误以及事务回滚。
import sqlite3
import os
import datetime
db_name = 'comprehensive_update_db.db'
if os.path.exists(db_name):
os.remove(db_name)
with sqlite3.connect(db_name, detect_types=sqlite3.PARSE_DECLTYPES) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("PRAGMA foreign_keys = ON;") # 确保外键生效
# --- 数据库结构和数据准备 (包含外键,定义 ON UPDATE 行为) ---
cursor.execute('''
CREATE TABLE IF NOT EXISTS categories (
category_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
product_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
price REAL CHECK(price > 0) DEFAULT 0.0,
stock INTEGER DEFAULT 0 CHECK(stock >= 0),
category_id INTEGER,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON UPDATE CASCADE -- 分类ID更新时,产品category_id也会更新
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS customers (
customer_id INTEGER PRIMARY KEY, -- 这里不使用 AUTOINCREMENT,以便演示手动修改PK和ON UPDATE CASCADE
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS orders (
order_id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id INTEGER NOT NULL,
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
total_amount REAL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON UPDATE CASCADE -- 客户ID更新时,其所有订单的customer_id也会更新
)
''')
# 插入数据
cursor.executemany("INSERT INTO categories (name) VALUES (?)", [("Electronics",), ("Books",), ("Home",)])
cursor.execute("SELECT category_id FROM categories WHERE name = 'Electronics'")
electronics_cat_id = cursor.fetchone()['category_id']
cursor.execute("SELECT category_id FROM categories WHERE name = 'Books'")
books_cat_id = cursor.fetchone()['category_id']
cursor.executemany("INSERT INTO products (name, price, stock, category_id) VALUES (?, ?, ?, ?)", [
("Laptop", 1200.50, 20, electronics_cat_id),
("Mouse", 25.00, 150, electronics_cat_id),
("Keyboard", 75.99, 80, electronics_cat_id),
("Python Book", 49.99, 50, books_cat_id),
("Old Mouse", 20.00, 10, electronics_cat_id) # 用于更新演示
])
cursor.executemany("INSERT INTO customers (customer_id, first_name, last_name, email) VALUES (?, ?, ?, ?)", [
(101, "Alice", "Smith", "alice@example.com"),
(102, "Bob", "Johnson", "bob@example.com"),
(103, "Charlie", "Brown", "charlie@example.com")
])
# 获取 ID 用于订单插入
alice_id = 101
bob_id = 102
cursor.execute("INSERT INTO orders (customer_id, order_date, total_amount) VALUES (?, ?, ?)",
(alice_id, datetime.datetime(2023, 1, 10), 1250.50))
order1_id = cursor.lastrowid
cursor.execute("INSERT INTO orders (customer_id, order_date, total_amount) VALUES (?, ?, ?)",
(bob_id, datetime.datetime(2023, 2, 15), 74.99))
order2_id = cursor.lastrowid
conn.commit() # 提交初始数据
print("数据库和初始数据已准备好。\n")
# --- 演示各种 UPDATE 操作 ---
print("--- 1. 更新单个列的简单更新 ---")
new_email = "alice.smith@newdomain.com"
target_cust_id = 101
cursor.execute("UPDATE customers SET email = ? WHERE customer_id = ?", (new_email, target_cust_id))
updated_count = cursor.rowcount
conn.commit()
print(f" 更新了 {updated_count} 个客户的邮箱。新邮箱: {new_email}")
cursor.execute("SELECT email FROM customers WHERE customer_id = ?", (target_cust_id,))
print(f" 验证:客户 ID {target_cust_id} 的邮箱现在是: {cursor.fetchone()['email']}")
print("\n--- 2. 更新多个列 ---")
product_id_to_update = 5 # Old Mouse
new_product_name = "New Ergonomic Mouse"
new_price = 35.50
new_stock = 100
cursor.execute("UPDATE products SET name = ?, price = ?, stock = ? WHERE product_id = ?",
(new_product_name, new_price, new_stock, product_id_to_update))
updated_count = cursor.rowcount
conn.commit()
print(f" 更新了 {updated_count} 个产品 (ID: {product_id_to_update}) 的多个属性。")
cursor.execute("SELECT name, price, stock FROM products WHERE product_id = ?", (product_id_to_update,))
row = cursor.fetchone()
print(f" 验证:产品 '{row['name']}',价格: ${row['price']:.2f}, 库存: {row['stock']}")
print("\n--- 3. 基于当前值的更新 (增加库存) ---")
current_stock_before = 0
product_id_for_stock = 3 # Keyboard
cursor.execute("SELECT stock FROM products WHERE product_id = ?", (product_id_for_stock,))
current_stock_before = cursor.fetchone()['stock']
print(f" 产品 ID {product_id_for_stock} (Keyboard) 原始库存: {current_stock_before}")
stock_to_add = 20
cursor.execute("UPDATE products SET stock = stock + ? WHERE product_id = ?", (stock_to_add, product_id_for_stock))
conn.commit()
cursor.execute("SELECT stock FROM products WHERE product_id = ?", (product_id_for_stock,))
current_stock_after = cursor.fetchone()['stock']
print(f" 增加了 {stock_to_add} 库存。新库存: {current_stock_after}")
print("\n--- 4. 触发 ON UPDATE CASCADE (更新客户ID,订单中的customer_id也更新) ---")
old_alice_id = 101
new_alice_id = 201 # 假设客户ID不是AUTOINCREMENT,可以手动修改
# 检查 Alice 原始订单
cursor.execute("SELECT order_id, customer_id FROM orders WHERE customer_id = ?", (old_alice_id,))
print(f" 更新前,客户ID {old_alice_id} 的订单: {[dict(row) for row in cursor.fetchall()]}")
cursor.execute("UPDATE customers SET customer_id = ? WHERE customer_id = ?", (new_alice_id, old_alice_id))
updated_cust_count = cursor.rowcount
conn.commit()
print(f" 更新了 {updated_cust_count} 个客户的 customer_id 从 {old_alice_id} 到 {new_alice_id}。")
# 检查 Alice 订单是否级联更新
cursor.execute("SELECT order_id, customer_id FROM orders WHERE customer_id = ?", (new_alice_id,))
print(f" 更新后,客户ID {new_alice_id} 的订单 (应已级联): {[dict(row) for row in cursor.fetchall()]}")
cursor.execute("SELECT order_id FROM orders WHERE customer_id = ?", (old_alice_id,))
print(f" 验证:原始客户ID {old_alice_id} 是否还有订单: {len(cursor.fetchall())} 条 (应为 0)。")
# 更新 categories 表的 primary key 演示 ON UPDATE CASCADE
# 由于 categories.category_id 是 AUTOINCREMENT,通常不手动修改,
# 但如果它是普通PRIMARY KEY,则可以演示类似效果。
# 这里为了简洁,不再额外演示 categories 的 CASCADE。
print("\n--- 5. 触发唯一性约束错误 (尝试将产品名称更新为已存在的值) ---")
product_id_to_err = 2 # Mouse
existing_product_name = "Laptop" # Laptop 已存在
try:
cursor.execute("UPDATE products SET name = ? WHERE product_id = ?", (existing_product_name, product_id_to_err))
conn.commit()
print(f" 成功更新产品 ID {product_id_to_err} 的名称为 '{existing_product_name}'。") # 不会执行
except sqlite3.IntegrityError as e:
print(f" 捕获到 IntegrityError: {e}")
print(f" 无法更新产品 ID {product_id_to_err} 的名称,因为名称 '{existing_product_name}' 已存在 (唯一性约束)。事务已回滚。")
conn.rollback()
cursor.execute("SELECT name FROM products WHERE product_id = ?", (product_id_to_err,))
print(f" 验证:产品 ID {product_id_to_err} 的名称仍然是: {cursor.fetchone()['name']}")
print("\n--- 6. 演示事务回滚 (更新然后回滚) ---")
product_id_for_rollback = 4 # Python Book
# 记录更新前产品信息
cursor.execute("SELECT name, price FROM products WHERE product_id = ?", (product_id_for_rollback,))
original_product_info = cursor.fetchone()
print(f" 事务前产品 '{original_product_info['name']}' 价格: ${original_product_info['price']:.2f}")
try:
print(f" 尝试更新产品 ID {product_id_for_rollback} 的价格和名称...")
cursor.execute("UPDATE products SET price = ?, name = ? WHERE product_id = ?",
(99.99, "New Python Programming Guide", product_id_for_rollback))
updated_count = cursor.rowcount
print(f" 更新了 {updated_count} 条产品记录。")
# 决定回滚
conn.rollback()
print(" 事务已回滚,更新操作已撤销。")
# 验证回滚后产品信息是否恢复
cursor.execute("SELECT name, price FROM products WHERE product_id = ?", (product_id_for_rollback,))
current_product_info = cursor.fetchone()
print(f" 回滚后产品 '{current_product_info['name']}' 价格: ${current_product_info['price']:.2f}")
if current_product_info['name'] == original_product_info['name'] and current_product_info['price'] == original_product_info['price']:
print(" 回滚成功。")
else:
print(" 回滚失败!")
except sqlite3.Error as e:
print(f" 发生错误: {e}")
conn.rollback()
print(" 事务已回滚。")
print(f"\n所有 `UPDATE` 子句演示完成,数据库 '{db_update_db}' 已更新。")
11. 总结
为您详细介绍了 Python SQLite 中更新数据 (UPDATE 子句) 的各个方面。
核心要点回顾:
UPDATE语句用于修改表中现有行的列值。SET子句定义要修改的列及其新值。WHERE子句是强制性的,用于指定要更新的特定行;省略WHERE子句将更新表中的所有行。UPDATE中的WHERE子句支持所有常见的 SQL 运算符进行复杂的条件判断。- 新值可以是常量、基于现有列的表达式或子查询的结果。
- 外键约束 (
ON UPDATE CASCADE,ON UPDATE SET NULL,ON UPDATE RESTRICT/NO ACTION) 决定了父表主键被更新时子表记录的响应行为。务必在连接时启用PRAGMA foreign_keys = ON;。 - 在 Python
sqlite3中执行UPDATE操作时,必须使用参数绑定来传递SET子句中的新值和WHERE子句中的动态值,以防止 SQL 注入。 - 事务管理 (
conn.commit()和conn.rollback()) 对于数据修改操作至关重要,确保了数据的一致性和可回滚性。 cursor.rowcount属性可以获取受影响的行数。- 处理
sqlite3.OperationalError(语法或连接问题) 和sqlite3.IntegrityError(约束违规)。 - 最佳实践包括:始终使用有条件的更新、参数绑定、启用外键、使用事务、先验证后更新、备份数据和在安全环境中测试。
通过这些全面而深入的知识,您将能够安全、高效、准确地在 SQLite 数据库中执行数据更新操作,并构建更健壮的 Python 数据库应用程序。
清理测试文件 (为确保后续测试环境干净)
import os
files_to_clean = [
'update_example.db',
'comprehensive_update_db.db'
]
for f in files_to_clean:
if os.path.exists(f):
os.remove(f)
print("\n所有测试数据库文件已清理。")
更多推荐

所有评论(0)