迁移 Oracle 到 PostgreSQL: 游标

七月 13, 2023

摘要:Oracle PL/SQL 和 PostgreSQL PL/pgSQL 都支持游标的功能。语法TYPE … IS REF CURSOR不受 PostgreSQL 支持。语法上的细微差异可能需要重写一些代码。PostgreSQL 不支持%ISOPEN%BULK_EXCEPTIONS%BULK_ROWCOUNT

Oracle 用法

PL/SQL 游标是指应用程序代码逻辑中可以迭代访问数据集的指针。数据集保存 SQL 语句返回的行。您可以在程序内使用命名游标引用活动的数据集。

有两种类型的 PL/SQL 游标:

  • 隐式游标是由 PL/SQL 自动构造和管理的会话游标,无需用户创建或定义。PL/SQL 每次运行SELECT或 DML 语句时都会打开一个隐式游标。隐式游标也称为 SQL 游标。
  • 显式游标是由用户创建、构造和管理的会话游标。游标是通过变量命名并将其与查询关联来声明和定义的。与隐式游标不同,你可以使用游标名称引用显式游标。显式游标也称为命名游标。

例子

以下示例演示了游标用法:

  1. 定义一个名为c1的显式 PL/SQL 游标。
  2. 游标运行 SQL 语句以从数据库返回行。
  3. PL/SQL 循环逐行从游标读取数据,并将值存储到两个变量中:v_lastnamev_jobid
  4. 循环使用了%NOTFOUND属性在从数据库中读取到最后一行后终止。
DECLARE
  CURSOR c1 IS
    SELECT last_name, job_id FROM employees
    WHERE REGEXP_LIKE (job_id, 'S[HT]_CLERK')
    ORDER BY last_name;
    v_lastname employees.last_name%TYPE; -- variable to store last_name
    v_jobid employees.job_id%TYPE; -- variable to store job_id
  BEGIN
    OPEN c1;
    LOOP -- Fetches 2 columns into variables
      FETCH c1 INTO v_lastname, v_jobid;
      EXIT WHEN c1%NOTFOUND;
    END LOOP;
  CLOSE c1;
END;
  1. 使用FOR循环定义隐式 PL/SQL 游标。
  2. 游标运行查询并将返回的值存储到记录中。
  3. 循环迭代访问游标数据集并打印结果。
BEGIN
FOR item IN
  (SELECT last_name, job_id FROM employees WHERE job_id LIKE '%MANAGER%'
    AND manager_id > 400 ORDER BY last_name) LOOP
    DBMS_OUTPUT.PUT_LINE('Name = ' || item.last_name || ', Job = ' || item.job_id);
  END LOOP;
END;
/

有关详细信息,请参阅 Oracle 文档中显式游标的声明和定义隐式游标属性

PostgreSQL 用法

与 Oracle PL/SQL 游标类似,PostgreSQL 具有 PL/pgSQL 游标,使您能够对从数据库读取的行迭代业务逻辑。它们可以封装查询并一次读取几行查询结果。PL/pgSQL 中对游标的所有访问都是通过游标变量执行的,这些游标变量始终是 refcursor 数据类型。

通过将 PL/pgSQL 游标声明为 refcursor 类型的变量来创建它。

声明游标的示例

在 PL/pgSQL 中声明一个游标以用于任何查询。

DECLARE c1 refcursor;

变量 c1 是未绑定的,因为它未绑定到任何特定查询。

在 PL/pgSQL 中使用绑定查询声明一个游标。

DECLARE c2 CURSOR FOR SELECT * FROM employees;

在以下示例中,您可以替换FORIS以实现 Oracle 兼容性。在 PL/pgSQL 中声明一个游标以用于任何查询。

DECLARE c3 CURSOR (var1 integer) FOR SELECT * FROM employees where id = var1;
  • 打开游标时,id 变量将替换为整数参数值。
  • 声明指定为SCROLL的游标时,游标可以向后滚动。
  • 如果指定NO SCROLL,则拒绝向后提取。

使用SCROLL选项声明向后滚动兼容的游标。

DECLARE c3 SCROLL CURSOR FOR SELECT id, name FROM employees;
  • SCROLL指定可以向后检索行。NO SCROLL指定不能向后检索行。
  • 根据查询的运行计划的复杂性,SCROLL可能会产生性能问题。
  • 当查询包含FOR UPDATEFOR SHARE时,不允许向后提取。

打开游标的示例

打开声明为“未绑定”的游标变量,并指定要运行的查询。

OPEN c1 FOR SELECT * FROM employees WHERE id = emp_id;

打开声明为“未绑定”的游标变量,并指定要作为字符串表达式运行的查询。这种方法提供了更大的灵活性。

OPEN c1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1', tabname) USING keyvalue;

可以使用format()USING将参数值插入到动态命令中。例如,使用format()将表名插入到查询中。col1 的比较值是使用USING参数插入的。

打开在声明游标时绑定到查询且声明为接受参数的游标。

DO $$
DECLARE
  c3 CURSOR (var1 integer) FOR SELECT * FROM employees where id = var1;
BEGIN
  OPEN c3(var1 := 42);
END$$;

对于 c3 游标,提供参数值表达式。如果未将游标声明为接受参数,则可以在游标外部指定参数。

DO $$
DECLARE
  var1 integer;
  c3 CURSOR FOR SELECT * FROM employees where id = var1;
BEGIN
  var1 := 1;
  OPEN c3;
END$$;

读取游标的示例

PL/pgSQL 的FETCH命令将游标中的下一行检索到变量中。将从游标c3返回的值提取到行变量中。

DO $$
DECLARE
  c3 CURSOR FOR SELECT * FROM employees;
  rowvar employees%ROWTYPE;
BEGIN
  OPEN c3;
  FETCH c3 INTO rowvar;
END$$;

将从 c3 游标返回的值提取为两种标量数据类型。

DO $$
DECLARE
  c3 CURSOR FOR SELECT id, name FROM employees;
  emp_id integer;
  emp_name varchar;
BEGIN
  OPEN c3;
  FETCH c3 INTO emp_id, emp_name;
END$$;

PL/pgSQL 在从游标获取数据时支持特殊的方向子句,你可以使用NEXTPRIORFIRSTLASTABSOLUTE countRELATIVE countFORWARDBACKWARD这些参数。省略方向等效于指定NEXT。例如,将最后一行从游标提取到声明的变量中。

DO $$
DECLARE
  c3 CURSOR FOR SELECT id, name FROM employees;
  emp_id integer;
  emp_name varchar;
BEGIN
  OPEN c3;
  FETCH LAST FROM c3 INTO emp_id, emp_name;
END$$;

有关详细信息,请参阅 PostgreSQL 文档中的 FETCH

关闭游标的示例

使用CLOSE命令关闭 PL/pgSQL 游标。

DO $$
DECLARE
  c3 CURSOR FOR SELECT id, name FROM employees;
  emp_id integer;
  emp_name varchar;
BEGIN
  OPEN c3;
  FETCH LAST FROM c3 INTO emp_id, emp_name;
  CLOSE c3;
END$$;

循环访问游标的示例

PL/pgSQL 支持检测游标何时没有更多数据要返回,并且可以与循环结合使用以迭代游标引用的所有行。

以下 PL/pgSQL 代码使用循环从游标中获取所有行,然后在读取最后一条记录后退出(使用EXIT WHEN NOT FOUND)。

PL/pgSQL 支持检测游标何时没有更多数据要返回,并且可以与循环结合使用以迭代游标引用的所有行。

以下 PL/pgSQL 代码使用循环从游标中获取所有行,然后在读取最后一条记录后退出(使用EXIT WHEN NOT FOUND)。

DO $$
DECLARE
  c3 CURSOR FOR SELECT * FROM employees;
  rowvar employees%ROWTYPE;
BEGIN
OPEN c3;
  LOOP
    FETCH FROM c3 INTO rowvar;
    EXIT WHEN NOT FOUND;
  END LOOP;
  CLOSE c3;
END$$;

在不读取数据的情况下移动游标的示例

MOVE重新定位游标而不检索任何数据,其工作方式类似于命令FETCH,但它仅在数据集中重新定位游标,而不返回游标移动到的行。可以检查特殊变量FOUND以确定是否有下一行。

移动到游标 c3 的最后一行(空值或未找到数据)。

MOVE LAST FROM c3;

将光标向后移动两条记录。

MOVE RELATIVE -2 FROM c3;

将 c3 光标向前移动两条记录。

MOVE FORWARD 2 FROM c3;

更新或删除当前示例

当游标位于表行上时,可以更新或删除该行。游标的查询可以选择哪些内容才能使这种类型的 DML 成功。

例如,更新 C3 游标指向的当前行。

UPDATE employee SET salary = salary * 1.2 WHERE CURRENT OF c3;

使用隐式游标(FOR 循环查询)的示例

DO $$
DECLARE
  item RECORD;
BEGIN
  FOR item IN (
    SELECT last_name, job_id
    FROM employees
    WHERE job_id LIKE '%MANAGER%'
    AND manager_id > 400
    ORDER BY last_name
  )
  LOOP
    RAISE NOTICE 'Name = %, Job=%', item.last_name, item.job_id;
  END LOOP;
END $$;

总结

步骤 Oracle PL/SQL PostgreSQL PL/pgSQL
声明绑定显式游标 CURSOR c1 IS
SELECT * FROM employees;
c2 CURSOR FOR
SELECT * FROM employees;
打开游标 OPEN c1; OPEN c2;
将光标移动到下一行并提取到记录变量中(rowvar 是在 DECLARE 部分中声明的) FETCH c1 INTO rowvar; FETCH c2 INTO rowvar;
将光标移动到下一行,并将值提取到多个标量数据类型(emp_id,emp_name,salary 是在 DECLARE 部分中声明) FETCH c1 INTO emp_id, emp_name, salary; FETCH c2 INTO emp_id, emp_name, salary;
使用循环循环访问隐式游标 FOR item IN (
SELECT last_name, job_id
FROM employees
WHERE job_id LIKE '%CLERK%'
AND manager_id > 120 ORDER BY last_name )
LOOP
<< do something >>
END LOOP;
FOR item IN (
SELECT last_name, job_id
FROM employees
WHERE job_id LIKE '%CLERK%'
AND manager_id > 120 ORDER BY last_name )
LOOP
<< do something >>
END LOOP;
声明带有变量的游标 CURSOR c1 (key NUMBER) IS
SELECT * FROM employees WHERE id = key;
C2 CURSOR (key integer) FOR
SELECT * FROM employees WHERE id = key;
打开包含变量的游标 OPEN c1(2); OPEN c2(2); or OPEN c2(key := 2);
未找到数据后退出循环 EXIT WHEN c1%NOTFOUND; EXIT WHEN NOT FOUND;
检测游标的数据集中是否仍有行 %FOUND FOUND
确定受任何 DML 语句影响的行数 %BULK_ROWCOUNT 不支持,但您可以在运行每个 DML 后调用GET DIAGNOSTICS integer_var = ROW_COUNT;并将结果保存在数组中
确定哪个 DML 运行失败并显示相关错误代码 %BULK_EXCEPTIONS 不适用
检测光标是否打开 %ISOPEN 不适用
检测游标是否在其数据集中没有剩余行 %NOTFOUND NOT FOUND
返回受游标影响的行数 %ROWCOUNT GET DIAGNOSTICS integer_var = ROW_COUNT;

有关详细信息,请参阅 PostgreSQL 文档中的游标基本声明