修改数据:INSERT、UPDATE、DELETE

INSERT、UPDATE 和 DELETE 会修改已保存的数据。本课同时说明基本写法、如何确认影响范围,以及如何在需要时撤销修改。

这一课会修改数据。改的是你自己的副本:示例数据库载入在你的浏览器里,不会离开,所以这里的一切都可以放心运行、放心出错。想从头再来,就从欢迎页重新载入示例。

INSERT

写出列名,再给每个列一个值。没写的列用默认值——这里 added_at 默认取当前时间。

1

INSERT INTO wishlists (customer_id, product_id)
VALUES (1, 288);
在 Planche 中运行

1 行被修改

浏览器会另开一个标签页打开 Planche,示例数据库已经载入,这条查询也已经放好。本页其他示例都会复用同一个标签页;在你按下 F9 之前,什么都不会执行。

一行被修改。明确写出列名、而不是依赖表里的列顺序,正是这条语句在别人加了一个列之后还能继续正常工作的原因。

数据库会拒绝什么

把完全相同的语句再跑一遍。

2

-- The same statement a second time. (customer_id, product_id) is the
-- primary key of wishlists, so the database refuses the duplicate.
INSERT INTO wishlists (customer_id, product_id)
VALUES (1, 288);
在 Planche 中运行

错误

UNIQUE constraint failed: wishlists.customer_id, wishlists.product_id

报错,什么也没变。wishlists 上有 PRIMARY KEY (customer_id, product_id),所以同一组合不能存在两次——是约束拒绝了它。约束不是需要绕开的障碍,它是在阻止你应用里的一个 bug 变成永久错误的数据。

UPDATE,以及那个最要紧的子句

3

UPDATE customers
SET loyalty_points = loyalty_points + 100
WHERE id = 1;
在 Planche 中运行

1 行被修改

两个习惯能让这种事难得多。第一,先写 WHERE 再写 SET。第二,先当成 SELECT 跑一遍——一样的 FROM、一样的 WHERE——看清返回什么,再去改。

第二件事 Planche 能替你做。打开设置 › 安全 › 预览受影响的行,UPDATE 或 DELETE 会先被改写成对应的 SELECT,数好条数并展示给你,然后才执行。只读连接和给生产环境连接标颜色,也在同一处。

4

SELECT id, first_name, last_name, loyalty_points
FROM customers
WHERE id = 1;
在 Planche 中运行

结果 — 1 行

idfirst_namelast_nameloyalty_points
1LiamFischer3619

DELETE

5

DELETE FROM wishlists
WHERE customer_id = 1
  AND product_id = 288;
在 Planche 中运行

1 行被修改

用子查询挑出要动的行

UPDATE 和 DELETE 的 WHERE 就是普通的 WHERE,所以子查询那一课的内容在这里全都能用。要根据另一张表的内容去改这张表的行,靠的就是子查询。

6

-- Which rows to change is its own question, and a subquery can answer it.
UPDATE customers
SET loyalty_points = loyalty_points + 50
WHERE id IN (
  SELECT customer_id
  FROM orders
  WHERE status = 'refunded'
    AND total > 2000
);
在 Planche 中运行

242 行被修改

有 242 位客户存在金额超过 2,000 的退款订单,于是这 242 行被改了。前面那个习惯照样适用:先当成 SELECT id FROM customers WHERE id IN (...) 跑一遍数一数,因为出错的地方就在子查询里。

7

DELETE FROM wishlists
WHERE product_id IN (
  SELECT id FROM products WHERE stock_quantity = 0
);
在 Planche 中运行

260 行被修改

NOT EXISTS 和 EXISTS 在这里也能用,而取反的场景更应该选它们,理由子查询那一课已经讲过。真正不可移植的是在 UPDATE 或 DELETE 里直接连接——每个引擎的写法都不一样,其中一个干脆做不到。方言页会把五种都讲一遍。

事务

事务把若干语句捆在一起,要么全都发生,要么全都不发生——而且在你提交之前,还允许你看看结果再改主意。

8

BEGIN;

UPDATE coupons
SET used_count = used_count + 1
WHERE code = 'WELCOME10';

SELECT code, used_count FROM coupons WHERE code = 'WELCOME10';

ROLLBACK;
在 Planche 中运行

结果 — 1 行

codeused_count
WELCOME1063

BEGIN 开启事务,SELECT 里优惠券数量已经加上了,ROLLBACK 又把它放了回去。把 ROLLBACK 换成 COMMIT,改动就变成永久的。自己确认一下计数没有变:

9

SELECT code, used_count FROM coupons WHERE code = 'WELCOME10';
在 Planche 中运行

结果 — 1 行

codeused_count
WELCOME1062

在真实数据库上做有风险的改动时,这就是那件对的工具:开事务、执行语句、确认行数和预期一致,然后才提交。Planche 把它做成了一种模式——连接 › 手动事务会一直开着一个事务,并把提交和回滚放到工具栏上,于是你跑的每条语句在你表态之前都只是暂时的。

现在你能做什么

除了查清一条查询为什么慢,其他都能做了——那是最后一课。

自己动手写

  1. 把所有缺货的商品下架。执行之前先确认你预期会改动多少行。

    查看答案

    查看答案

    UPDATE products
    SET is_active = 0
    WHERE stock_quantity = 0;
    在 Planche 中运行

    40 行被修改

    40 行。先跑一条 SELECT COUNT(*) FROM products WHERE stock_quantity = 0,习惯的全部就在这里:语句执行前你就知道那个数字,于是对不上的条数是一个警告,而不是一个"发现"。

  2. 删掉已读、且早于 2025 年的通知。

    查看答案

    查看答案

    DELETE FROM notifications
    WHERE is_read = 1
      AND created_at < '2025-01-01';
    在 Planche 中运行

    1202 行被修改

    1,202 行。两个条件都不能少——去掉 is_read = 1 会把未读通知也删了,而这是 DELETE,除非你先开了事务,否则没有撤销可言。