Oracle数据更新 #

一、UPDATE语句概述 #

1.1 基本语法 #

sql
-- UPDATE语句基本语法
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

-- 使用子查询更新
UPDATE table_name
SET column = (SELECT value FROM another_table WHERE ...)
WHERE condition;

1.2 示例表结构 #

sql
-- 创建示例表
CREATE TABLE employees (
    employee_id NUMBER(6) PRIMARY KEY,
    first_name VARCHAR2(50),
    last_name VARCHAR2(50),
    email VARCHAR2(100),
    salary NUMBER(8,2),
    department_id NUMBER(4),
    manager_id NUMBER(6),
    hire_date DATE
);

-- 插入测试数据
INSERT INTO employees VALUES (1, 'John', 'Doe', 'john@example.com', 5000, 10, 100, SYSDATE);
INSERT INTO employees VALUES (2, 'Jane', 'Smith', 'jane@example.com', 6000, 10, 100, SYSDATE);
INSERT INTO employees VALUES (3, 'Bob', 'Johnson', 'bob@example.com', 7000, 20, 101, SYSDATE);
INSERT INTO employees VALUES (4, 'Alice', 'Williams', 'alice@example.com', 8000, 20, 101, SYSDATE);
INSERT INTO employees VALUES (5, 'Charlie', 'Brown', 'charlie@example.com', 9000, 30, 102, SYSDATE);

二、基本更新 #

2.1 更新单个列 #

sql
-- 更新单个列
UPDATE employees
SET salary = 5500
WHERE employee_id = 1;

-- 更新多个列
UPDATE employees
SET salary = 6000, department_id = 20
WHERE employee_id = 2;

-- 使用表达式更新
UPDATE employees
SET salary = salary * 1.1
WHERE employee_id = 3;

-- 使用函数更新
UPDATE employees
SET email = LOWER(first_name || '.' || last_name || '@example.com')
WHERE employee_id = 4;

2.2 条件更新 #

sql
-- 使用WHERE条件
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10;

-- 使用多个条件
UPDATE employees
SET salary = salary * 1.2
WHERE department_id = 10 AND salary < 6000;

-- 使用IN条件
UPDATE employees
SET department_id = 40
WHERE employee_id IN (1, 2, 3);

-- 使用BETWEEN条件
UPDATE employees
SET salary = salary * 1.05
WHERE salary BETWEEN 5000 AND 7000;

-- 使用LIKE条件
UPDATE employees
SET email = REPLACE(email, '@example.com', '@newcompany.com')
WHERE email LIKE '%@example.com';

-- 使用子查询条件
UPDATE employees
SET salary = salary * 1.1
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1700);

2.3 更新NULL值 #

sql
-- 更新为NULL
UPDATE employees
SET manager_id = NULL
WHERE employee_id = 5;

-- 更新NULL值为默认值
UPDATE employees
SET manager_id = 100
WHERE manager_id IS NULL;

-- 使用NVL处理NULL
UPDATE employees
SET salary = NVL(salary, 0) + 1000;

三、使用子查询更新 #

3.1 标量子查询更新 #

sql
-- 使用子查询更新单列
UPDATE employees e
SET salary = (
    SELECT AVG(salary)
    FROM employees
    WHERE department_id = e.department_id
)
WHERE employee_id = 1;

-- 使用子查询更新多列
UPDATE employees e
SET (salary, department_id) = (
    SELECT salary, department_id
    FROM employees_backup
    WHERE employee_id = e.employee_id
)
WHERE EXISTS (
    SELECT 1
    FROM employees_backup
    WHERE employee_id = e.employee_id
);

3.2 相关子查询更新 #

sql
-- 相关子查询更新
UPDATE employees e
SET salary = (
    SELECT MAX(salary)
    FROM employees
    WHERE department_id = e.department_id
)
WHERE salary < (
    SELECT AVG(salary)
    FROM employees
    WHERE department_id = e.department_id
);

-- 使用EXISTS确保子查询返回值
UPDATE employees e
SET department_name = (
    SELECT department_name
    FROM departments d
    WHERE d.department_id = e.department_id
)
WHERE EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_id = e.department_id
);

3.3 使用WITH子句更新 #

sql
-- 使用WITH子句(12c+)
WITH dept_avg AS (
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
)
UPDATE employees e
SET salary = (
    SELECT avg_salary
    FROM dept_avg d
    WHERE d.department_id = e.department_id
)
WHERE department_id = 10;

四、多表更新 #

4.1 使用可更新视图 #

sql
-- 创建可更新视图
CREATE OR REPLACE VIEW emp_dept_view AS
SELECT 
    e.employee_id,
    e.first_name,
    e.last_name,
    e.salary,
    e.department_id,
    d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;

-- 通过视图更新
UPDATE emp_dept_view
SET salary = salary * 1.1
WHERE department_name = 'IT';

-- 注意:只能更新基础表的列,不能更新连接列

4.2 使用内联视图更新 #

sql
-- 使用内联视图更新
UPDATE (
    SELECT e.salary, e.department_id
    FROM employees e
    WHERE e.department_id = 10
)
SET salary = salary * 1.1;

-- 使用内联视图关联更新
UPDATE (
    SELECT e.salary, d.max_salary
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
)
SET salary = max_salary
WHERE salary < max_salary;

4.3 使用MERGE语句 #

sql
-- MERGE语句:合并插入和更新
MERGE INTO employees e
USING employees_temp t
ON (e.employee_id = t.employee_id)
WHEN MATCHED THEN
    UPDATE SET 
        e.first_name = t.first_name,
        e.last_name = t.last_name,
        e.salary = t.salary
WHEN NOT MATCHED THEN
    INSERT (employee_id, first_name, last_name, salary)
    VALUES (t.employee_id, t.first_name, t.last_name, t.salary);

-- MERGE带条件
MERGE INTO employees e
USING employees_temp t
ON (e.employee_id = t.employee_id)
WHEN MATCHED THEN
    UPDATE SET 
        e.salary = t.salary
    WHERE e.salary != t.salary
WHEN NOT MATCHED THEN
    INSERT (employee_id, first_name, last_name, salary)
    VALUES (t.employee_id, t.first_name, t.last_name, t.salary);

-- MERGE带删除
MERGE INTO employees e
USING employees_temp t
ON (e.employee_id = t.employee_id)
WHEN MATCHED THEN
    UPDATE SET 
        e.salary = t.salary
    DELETE WHERE t.active = 'N'
WHEN NOT MATCHED THEN
    INSERT (employee_id, first_name, last_name, salary)
    VALUES (t.employee_id, t.first_name, t.last_name, t.salary);

五、特殊更新 #

5.1 使用CASE表达式 #

sql
-- 使用CASE表达式条件更新
UPDATE employees
SET salary = CASE
    WHEN department_id = 10 THEN salary * 1.1
    WHEN department_id = 20 THEN salary * 1.15
    WHEN department_id = 30 THEN salary * 1.2
    ELSE salary * 1.05
END;

-- 复杂CASE更新
UPDATE employees
SET 
    salary = CASE
        WHEN salary < 5000 THEN salary * 1.2
        WHEN salary BETWEEN 5000 AND 8000 THEN salary * 1.1
        ELSE salary * 1.05
    END,
    department_id = CASE
        WHEN salary < 5000 THEN 10
        WHEN salary BETWEEN 5000 AND 8000 THEN 20
        ELSE 30
    END;

5.2 使用DECODE函数 #

sql
-- 使用DECODE函数更新
UPDATE employees
SET department_id = DECODE(
    department_id,
    10, 20,
    20, 30,
    30, 40,
    department_id
);

-- 使用DECODE更新薪资
UPDATE employees
SET salary = DECODE(
    job_id,
    'IT_PROG', salary * 1.1,
    'SA_REP', salary * 1.15,
    'SA_MAN', salary * 1.2,
    salary
);

5.3 使用RETURNING子句 #

sql
-- 返回更新后的值
VARIABLE v_old_salary NUMBER;
VARIABLE v_new_salary NUMBER;

UPDATE employees
SET salary = salary * 1.1
WHERE employee_id = 1
RETURNING 
    salary / 1.1,  -- 旧值
    salary         -- 新值
INTO 
    :v_old_salary, 
    :v_new_salary;

PRINT v_old_salary;
PRINT v_new_salary;

-- PL/SQL中使用RETURNING
DECLARE
    v_old_salary NUMBER;
    v_new_salary NUMBER;
BEGIN
    UPDATE employees
    SET salary = salary * 1.1
    WHERE employee_id = 1
    RETURNING salary / 1.1, salary INTO v_old_salary, v_new_salary;
    
    DBMS_OUTPUT.PUT_LINE('Old Salary: ' || v_old_salary);
    DBMS_OUTPUT.PUT_LINE('New Salary: ' || v_new_salary);
END;
/

六、批量更新 #

6.1 使用FORALL批量更新 #

sql
-- PL/SQL中使用FORALL批量更新
DECLARE
    TYPE id_array IS TABLE OF NUMBER;
    TYPE salary_array IS TABLE OF NUMBER;
    
    v_ids id_array;
    v_salaries salary_array;
BEGIN
    -- 获取需要更新的数据
    SELECT employee_id, salary * 1.1
    BULK COLLECT INTO v_ids, v_salaries
    FROM employees
    WHERE department_id = 10;
    
    -- 批量更新
    FORALL i IN 1..v_ids.COUNT
        UPDATE employees
        SET salary = v_salaries(i)
        WHERE employee_id = v_ids(i);
    
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Updated ' || SQL%ROWCOUNT || ' rows');
END;
/

6.2 使用BULK COLLECT和FORALL #

sql
-- 批量更新示例
DECLARE
    CURSOR c_emp IS
        SELECT employee_id, salary
        FROM employees
        WHERE department_id = 10
        FOR UPDATE;
    
    TYPE emp_rec_type IS TABLE OF c_emp%ROWTYPE;
    v_emps emp_rec_type;
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp BULK COLLECT INTO v_emps LIMIT 100;
        
        FORALL i IN 1..v_emps.COUNT
            UPDATE employees
            SET salary = v_emps(i).salary * 1.1
            WHERE CURRENT OF c_emp;
        
        EXIT WHEN v_emps.COUNT < 100;
    END LOOP;
    CLOSE c_emp;
    
    COMMIT;
END;
/

七、更新性能优化 #

7.1 使用索引优化更新 #

sql
-- 确保WHERE条件使用索引
-- 创建索引
CREATE INDEX idx_emp_dept ON employees(department_id);

-- 使用索引列更新
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10;  -- 使用索引

-- 查看执行计划
EXPLAIN PLAN FOR
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

7.2 批量更新优化 #

sql
-- 1. 禁用索引
ALTER INDEX idx_emp_salary UNUSABLE;

-- 2. 批量更新
UPDATE employees SET salary = salary * 1.1;

-- 3. 重建索引
ALTER INDEX idx_emp_salary REBUILD;

-- 使用并行更新
UPDATE /*+ PARALLEL(employees, 4) */ employees
SET salary = salary * 1.1
WHERE department_id = 10;

7.3 减少日志 #

sql
-- 使用NOLOGGING减少日志(对直接路径操作有效)
ALTER TABLE employees NOLOGGING;

-- 对于大量更新,考虑使用CTAS重建表
CREATE TABLE employees_new NOLOGGING AS
SELECT 
    employee_id,
    first_name,
    last_name,
    email,
    salary * 1.1 AS salary,
    department_id
FROM employees;

-- 替换原表
DROP TABLE employees;
RENAME employees_new TO employees;

-- 重建索引和约束

八、更新错误处理 #

8.1 处理更新异常 #

sql
-- 捕获更新异常
BEGIN
    UPDATE employees
    SET salary = -100
    WHERE employee_id = 1;
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE = -2290 THEN
            DBMS_OUTPUT.PUT_LINE('Check constraint violated');
        ELSE
            RAISE;
        END IF;
END;
/

-- 检查更新行数
DECLARE
    v_rows_updated NUMBER;
BEGIN
    UPDATE employees
    SET salary = salary * 1.1
    WHERE department_id = 99;
    
    v_rows_updated := SQL%ROWCOUNT;
    
    IF v_rows_updated = 0 THEN
        DBMS_OUTPUT.PUT_LINE('No rows updated');
    ELSE
        DBMS_OUTPUT.PUT_LINE(v_rows_updated || ' rows updated');
    END IF;
END;
/

8.2 使用DML ERROR LOGGING #

sql
-- 创建错误日志表
EXEC DBMS_ERRLOG.CREATE_ERROR_LOG('employees', 'err_employees');

-- 使用LOG ERRORS记录错误
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10
LOG ERRORS INTO err_employees REJECT LIMIT UNLIMITED;

-- 查看错误记录
SELECT * FROM err_employees;

九、更新注意事项 #

9.1 更新陷阱 #

sql
-- 陷阱1:忘记WHERE条件
-- 危险操作!更新所有行
-- UPDATE employees SET salary = 0;

-- 安全做法:先查询再更新
SELECT COUNT(*) FROM employees WHERE department_id = 10;
-- 确认数量后再执行更新
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;

-- 陷阱2:子查询返回多行
-- 错误:子查询返回多行
-- UPDATE employees e
-- SET department_id = (
--     SELECT department_id FROM departments WHERE location_id = 1700
-- );

-- 正确:使用单行子查询或聚合
UPDATE employees e
SET department_id = (
    SELECT MIN(department_id) FROM departments WHERE location_id = 1700
);

-- 陷阱3:更新主键
-- 可能导致外键约束失败
-- UPDATE departments SET department_id = 100 WHERE department_id = 10;

9.2 并发更新处理 #

sql
-- 使用SELECT FOR UPDATE锁定行
SELECT * FROM employees
WHERE employee_id = 1
FOR UPDATE;

-- 更新锁定行
UPDATE employees
SET salary = salary * 1.1
WHERE employee_id = 1;

COMMIT;

-- 使用NOWAIT避免等待
SELECT * FROM employees
WHERE employee_id = 1
FOR UPDATE NOWAIT;

-- 使用SKIP LOCKED跳过锁定行
SELECT * FROM employees
WHERE employee_id = 1
FOR UPDATE SKIP LOCKED;

9.3 更新触发器 #

sql
-- 创建更新触发器
CREATE OR REPLACE TRIGGER trg_employees_update
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < :OLD.salary THEN
        RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be decreased');
    END IF;
    
    :NEW.salary := ROUND(:NEW.salary, 2);
END;
/

-- 测试触发器
UPDATE employees SET salary = 4000 WHERE employee_id = 1;  -- 会报错

十、总结 #

更新数据要点:

更新方式 语法 适用场景
单表更新 UPDATE … SET … WHERE 简单更新
子查询更新 SET column = (SELECT …) 关联更新
MERGE MERGE INTO … WHEN MATCHED 合并更新
批量更新 FORALL PL/SQL批量处理

最佳实践:

  1. 始终使用WHERE条件
  2. 先查询确认再更新
  3. 使用事务确保数据一致性
  4. 大批量更新考虑性能优化
  5. 使用错误日志记录异常

下一步,让我们学习数据删除!

最后更新:2026-03-27