(课堂笔记)游标与动态SQL(EXECUTE IMMEDIATE):使用 CHR(39) 替代拼接单引号

本文系统介绍了Oracle数据库中的游标与动态SQL技术。


主要内容包括:

  1. 游标概念与分类(隐式/显式游标)及其四大属性(ISOPEN/FOUND/NOTFOUND/ROWCOUNT);
  2. 显式游标的使用方法,包括定义、遍历和参数传递;
  3. 动态SQL的EXECUTE IMMEDIATE语法,重点说明SELECT...INTO在动态SQL中的特殊写法;
  4. 批量数据处理技巧和注意事项。

文章通过丰富示例演示了游标遍历、批量提交、动态表操作等典型场景,并对比了静态SQL与动态SQL的差异,为数据库开发提供了实用指导。


(课堂笔记)游标与动态SQL


🧩 一、游标(CURSOR)

1. 什么是游标?

  • 游标是指向数据库表中每一行数据的指针

  • 用于逐行处理查询结果集


2. 游标分类

类型 说明
隐式游标 自动创建,执行 DML(INSERT/UPDATE/DELETE)或 SELECT INTO 时自动生成
显式游标 开发者手动定义,指向一个 SELECT 语句的结果集

隐式游标
项目 说明
触发时机 执行 DML(UPDATE/DELETE/INSERT)或 SELECT INTO 时自动创建
开发者操作 无需定义,直接使用
属性引用 SQL%属性名
显式游标
项目 说明
定义方式 CURSOR 游标名 IS SELECT 语句;
本质 类似一个大的变量 ,用于接收一个表格(结果集)
使用场景 批量提交、批量更新、逐行处理数据
属性引用 游标名%属性名

3. 游标的四大属性(非常重要)

属性 类型 说明
%ISOPEN 布尔值 游标是否打开
%FOUND 布尔值 是否找到数据行
%NOTFOUND 布尔值 是否未找到数据行
%ROWCOUNT 数字 已遍历的行数
使用方式:
  • 隐式游标:SQL%属性名

  • 显式游标:游标名%属性名


🧪 二、隐式游标示例

sql

复制代码
BEGIN
  UPDATE EMP SET SAL = SAL + 100 WHERE DEPTNO = 10;

  IF SQL%FOUND THEN
    DBMS_OUTPUT.put_line('有数据被更新!');
    DBMS_OUTPUT.PUT_LINE('总共有:' || SQL%ROWCOUNT || ' 行数据被更新。');
  END IF;

  COMMIT;
END;

🛠 三、显式游标

创建语法(放在 BEGIN 之前)

sql

复制代码
CURSOR 游标名(参数名 参数类型)
IS
SELECT 语句;

示例1:遍历 20 号部门员工

sql

复制代码
CREATE OR REPLACE PROCEDURE P_005 IS
  CURSOR CUR_001 IS
    SELECT * FROM EMP WHERE DEPTNO = 20;
BEGIN
  FOR I IN CUR_001 LOOP
    DBMS_OUTPUT.PUT_LINE(I.ENAME || '--' || I.SAL);
  END LOOP;
END;

示例2:根据传入员工编号,打印其所在部门所有员工

sql

复制代码
CREATE OR REPLACE PROCEDURE P_007(P_NUM NUMBER) IS
  VVVV NUMBER;
  CURSOR CUR_002(P_DEPTNO NUMBER) IS
    SELECT ENAME FROM EMP WHERE DEPTNO = P_DEPTNO;
BEGIN
  SELECT DEPTNO INTO VVVV FROM EMP WHERE EMPNO = P_NUM;

  FOR I IN CUR_002(VVVV) LOOP
    DBMS_OUTPUT.PUT_LINE(I.ENAME);
  END LOOP;
END;

示例3:返回员工所在部门的所有员工姓名(拼接)

sql

复制代码
CREATE OR REPLACE FUNCTION KKK(P_EMPNO NUMBER) RETURN VARCHAR2 IS
  V_DEPTNO NUMBER;
  CURSOR CUR_001(P_DEPT NUMBER) IS
    SELECT ENAME FROM EMP WHERE DEPTNO = P_DEPT;
  V_RES VARCHAR2(1000) := '';
BEGIN
  SELECT DEPTNO INTO V_DEPTNO FROM EMP WHERE EMPNO = P_EMPNO;

  FOR I IN CUR_001(V_DEPTNO) LOOP
    V_RES := V_RES || I.ENAME || ',';
  END LOOP;

  V_RES := SUBSTR(V_RES, 1, LENGTH(V_RES) - 1);
  RETURN V_RES;
END;

对比记忆表

对比项 隐式游标 显式游标
是否需定义 ❌ 不需要 ✅ 需要 CURSOR...IS...
触发方式 自动(DML/SELECT INTO) 手动(OPEN/FOR循环)
结果集大小 通常单行 多行
典型场景 单行查询、DML操作 批量遍历、批量提交
属性引用 SQL%... 游标名%...

⚠️ 四、变量 vs 参数

类型 可否在内部赋值
参数(入参) ❌ 不能二次赋值
变量(IS 中定义) ✅ 可以任意赋值

sql

复制代码
CREATE OR REPLACE PROCEDURE PPP(P_NUM NUMBER) IS
BEGIN
  -- P_NUM := 2222;   ❌ 错误,参数不能赋值
  DBMS_OUTPUT.PUT_LINE(P_NUM);
END;

📦 五、综合练习(游标 + 批量插入)

创建空表并插入数据

sql

复制代码
-- 创建空表
CREATE TABLE EMP_0508 AS SELECT * FROM EMP WHERE 1=2;

-- 存储过程:插入指定部门数据
CREATE OR REPLACE PROCEDURE P_000(P_DEPT NUMBER) IS
  CURSOR CUR_001(T_DEPTNO NUMBER) IS
    SELECT * FROM EMP WHERE DEPTNO = T_DEPTNO;
BEGIN
  FOR I IN CUR_001(P_DEPT) LOOP
    INSERT INTO EMP_0508 VALUES 
      (I.EMPNO, I.ENAME, I.JOB, I.MGR, I.HIREDATE, I.SAL, I.COMM, I.DEPTNO);
  END LOOP;
  COMMIT;
END;

🔁 六、批量提交(避免 UNDO 表空间不足)

sql

复制代码
CREATE OR REPLACE PROCEDURE P_012 IS
  CURSOR CUR_012 IS SELECT * FROM EMP;
BEGIN
  FOR I IN CUR_012 LOOP
    INSERT INTO EMP_0508 VALUES (...);

    IF MOD(CUR_012%ROWCOUNT, 100000) = 0 THEN
      COMMIT;
    END IF;
  END LOOP;
  COMMIT;
END;

✅ 每 10 万条提交一次,适合大数据量操作。


⚡ 七、动态 SQL

使用场景:

  • SQL 语句结构不固定(如动态表名、条件)

  • 执行 DDL 语句(CREATE / DROP / TRUNCATE)


场景 说明 示例
场景1 SQL中存在变量,且变量在单引号内部 动态表名、动态条件
场景2 需要执行DDL语句 TRUNCATEDROPCREATE
sql 复制代码
-- ❌ 错误写法(变量在单引号内,不会被解析)
V_SQL := 'SELECT * FROM EMP WHERE ENAME = 'ENAME'';

-- ✅ 正确写法:变量用 || 拼接
V_SQL := 'SELECT * FROM EMP WHERE ENAME = ''' || V_ENAME || '''';
要点 说明
单引号处理 ''''(4个单引号)或 CHR(39) 表示一个单引号
变量/参数 严禁放在单引号里面,必须用 `

核心语法:

sql

sql 复制代码
-- 语法1:执行无返回值的SQL(DDL、DML)
EXECUTE IMMEDIATE SQL语句字符串;

-- 语法2:执行有返回值的SELECT,结果存入变量
EXECUTE IMMEDIATE SELECT语句字符串 INTO 变量1, 变量2...;

示例1:批量创建 100 张表

sql

复制代码
CREATE OR REPLACE PROCEDURE P_CRE_TABLE IS
  V_STR VARCHAR2(100);
BEGIN
  FOR I IN 1..100 LOOP
    V_STR := 'CREATE TABLE EMP__' || LPAD(I, 3, 0) || 
             ' AS SELECT * FROM EMP WHERE 1=2';
    EXECUTE IMMEDIATE V_STR;
  END LOOP;
END;

示例2:批量删除表

sql

复制代码
CREATE OR REPLACE PROCEDURE P_013 IS
  V_SQL VARCHAR2(1000);
BEGIN
  FOR I IN 1..100 LOOP
    V_SQL := 'DROP TABLE EMP__' || LPAD(I, 3, 0);
    EXECUTE IMMEDIATE V_SQL;
  END LOOP;
END;

示例3:TRUNCATE 表

sql

复制代码
CREATE OR REPLACE PROCEDURE P_014 IS
BEGIN
  EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_0508';
  INSERT INTO EMP_0508 SELECT * FROM EMP;
  COMMIT;
END;

示例4:动态 SELECT INTO

sql

复制代码
EXECUTE IMMEDIATE 'SELECT ENAME FROM EMP WHERE EMPNO = 7369' INTO V_ENAME;

🧠 常见考点 & 面试题

Q:游标的使用场景?

  • 批量提交

  • 批量更新

  • 逐行处理数据

Q:隐式游标和显式游标的区别?

隐式游标 显式游标
定义 自动 手动 CURSOR
使用场景 DML / SELECT INTO 多行结果集遍历
属性引用 SQL%... 游标名%...

Q:四大属性是什么?

  • %ISOPEN

  • %FOUND

  • %NOTFOUND

  • %ROWCOUNT


✅ 复习建议

  1. 掌握隐式和显式游标的区别及使用方式

  2. 熟练写出显式游标的定义 + FOR 循环遍历

  3. 理解四大属性的含义和输出时机

  4. 掌握动态 SQL 的写法,尤其是字符串拼接中的引号处理

  5. 会写批量提交的逻辑


如果 动态执行的 是 SELECT .. INTO 语句 则需要改写成 EXECUTE IMMEDIATE SQL语句字符串 INTO 变量1,变量2... 是什么意思


详细解释:动态 SQL 中的 SELECT ... INTO

你的笔记中这句话的意思是:

当你用动态SQL执行一个查询语句(SELECT)并且想把查询结果存储到变量中时,需要使用 EXECUTE IMMEDIATE ... INTO ... 语法


📌 为什么要这样写?

普通情况(静态SQL)

sql

复制代码
DECLARE
  V_ENAME VARCHAR2(100);
BEGIN
  -- 直接使用 SELECT INTO,结果存入变量
  SELECT ENAME INTO V_ENAME
  FROM EMP
  WHERE EMPNO = 7369;
  
  DBMS_OUTPUT.PUT_LINE(V_ENAME);  -- 输出:SMITH
END;

动态SQL情况

sql

复制代码
DECLARE
  V_ENAME VARCHAR2(100);
  V_SQL VARCHAR2(1000);
BEGIN
  -- SQL 语句被写成了字符串
  V_SQL := 'SELECT ENAME FROM EMP WHERE EMPNO = 7369';
  
  -- 必须用 EXECUTE IMMEDIATE ... INTO
  EXECUTE IMMEDIATE V_SQL INTO V_ENAME;
  
  DBMS_OUTPUT.PUT_LINE(V_ENAME);  -- 输出:SMITH
END;

🎯 核心语法

sql

复制代码
EXECUTE IMMEDIATE 'SELECT 语句字符串' INTO 变量1, 变量2, ...;

示例1:查询单个值

sql

复制代码
DECLARE
  V_ENAME VARCHAR2(100);
  V_SQL VARCHAR2(1000);
BEGIN
  V_SQL := 'SELECT ENAME FROM EMP WHERE EMPNO = 7499';
  EXECUTE IMMEDIATE V_SQL INTO V_ENAME;
  DBMS_OUTPUT.PUT_LINE(V_ENAME);  -- 输出:ALLEN
END;

示例2:查询多个值

sql

复制代码
DECLARE
  V_ENAME VARCHAR2(100);
  V_SAL NUMBER;
  V_SQL VARCHAR2(1000);
BEGIN
  V_SQL := 'SELECT ENAME, SAL FROM EMP WHERE EMPNO = 7499';
  EXECUTE IMMEDIATE V_SQL INTO V_ENAME, V_SAL;
  DBMS_OUTPUT.PUT_LINE(V_ENAME || '的工资是:' || V_SAL);
END;

💡 你笔记中的实际案例解析

sql

复制代码
CREATE OR REPLACE PROCEDURE P_021(X VARCHAR2) IS
  V_CNT NUMBER;
  V_SQL VARCHAR2(1000);
  V_ENAME VARCHAR2(100);
BEGIN 
  -- 先统计有多少符合条件的员工
  SELECT COUNT(1) INTO V_CNT
  FROM EMP WHERE INSTR(ENAME, X) > 0;

  IF V_CNT = 0 THEN 
    DBMS_OUTPUT.PUT_LINE('没有符合条件的员工!');
  ELSE 
    -- 循环取出每个员工
    FOR I IN 1..V_CNT LOOP
      -- 动态构造分页查询的SQL字符串
      V_SQL := 
        'SELECT ENAME
         FROM    
         (SELECT ENAME, ROW_NUMBER() OVER (ORDER BY 1) AS RN
          FROM EMP
          WHERE ENAME LIKE ' || CHR(39) || '%' || X || '%' || CHR(39) || ')
         WHERE RN = ' || I;
      
      -- ⭐ 关键:将动态查询的结果存入变量 V_ENAME
      EXECUTE IMMEDIATE V_SQL INTO V_ENAME;
      
      DBMS_OUTPUT.PUT_LINE(V_ENAME);
    END LOOP;
  END IF;
END;

⚠️ 注意事项

1. 查询结果必须只有一行

  • EXECUTE IMMEDIATE ... INTO 要求查询结果只能返回一行

  • 如果返回多行会报错 ORA-01422: exact fetch returns more than requested number of rows

2. 变量数量必须匹配

sql

复制代码
-- ❌ 错误:2个变量但只查询1列
EXECUTE IMMEDIATE 'SELECT ENAME FROM EMP WHERE EMPNO=7369' INTO V_ENAME, V_SAL;

-- ✅ 正确:查询2列对应2个变量
EXECUTE IMMEDIATE 'SELECT ENAME, SAL FROM EMP WHERE EMPNO=7369' INTO V_ENAME, V_SAL;

3. 字符串中的引号处理

sql

复制代码
-- 两种写法等价
-- 方法1:用两个单引号表示一个单引号
V_SQL := 'SELECT ENAME FROM EMP WHERE ENAME LIKE ''%S%''';

-- 方法2:用 CHR(39) 表示单引号(更清晰)
V_SQL := 'SELECT ENAME FROM EMP WHERE ENAME LIKE ' || CHR(39) || '%S%' || CHR(39);

📊 对比总结

场景 语法 示例
静态SQL SELECT ... INTO SELECT ENAME INTO V_ENAME FROM EMP...
动态SQL EXECUTE IMMEDIATE ... INTO EXECUTE IMMEDIATE V_SQL INTO V_ENAME

✅ 一句话记忆

动态SQL是字符串,要存结果加INTO,变量数量要匹配,查询必须单一行。

关联阅读推荐

DDL 语句必须使用动态SQL来执行,为什么动态SQL可以执行DDL

相关推荐
Mr.朱鹏2 小时前
基于 postgres_fdw 的跨库查询方案
java·数据库·spring boot·sql·spring·postgresql
AOwhisky3 小时前
MySQL 学习笔记(第二期):SQL 语言之库表操作与数据类型
linux·运维·数据库·笔记·sql·学习·mysql
2601_961194024 小时前
27考研刘晓燕资源
linux·sql·ubuntu·华为·pdf·.net
这个DBA有点耶13 小时前
云上运维新挑战:当数据库不再“看得见摸得着”
数据库·sql·程序人生·云原生·运维开发·学习方法·dba
九皇叔叔15 小时前
PostgreSQL/openGauss pg_stats 视图从入门到精通:统计信息、执行计划与慢 SQL 优化实战
数据库·sql·postgresql
南极企鹅15 小时前
MySQL间隙锁&临键锁
数据库·sql·mysql
l1t18 小时前
DeepSeek总结的使用 PEG 实现运行时可扩展的 SQL 解析器
数据库·sql
ZC跨境爬虫20 小时前
SQL学习日志 Day_3 :(SELECT查询语句入门)
数据库·sql·学习·oracle
tkevinjd20 小时前
事务、ACID与隔离
java·数据库·sql
wanghowie1 天前
26.v3 核心升级:语义层 + 指标体系——禁止 LLM 直连 SQL
数据库·sql