选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.1 已于 2016 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

39.12. 从Oracle PL/SQL 移植 #

这一节解释了PostgreSQLPL/pgSQL语言和 Oracle 的PL/SQL语言之间的差别,用以帮助那些从Oracle®向PostgreSQL移植应用的人。

PL/pgSQL在许多方面都与 PL/SQL 类似。它是一种具有块结构的命令式语言,所有变量都必须声明。赋值、循环和条件语句也都很相似。在从PL/SQL移植到PL/pgSQL时,应当记住以下主要差异:

  • 如果 SQL 命令中使用的名字既可能是表的列名,也可能是对函数中变量的引用,PL/SQL会将其当作列名。这对应于PL/pgSQLplpgsql.variable_conflict = use_column行为,这不是默认行为,如第 39.10.1 节所述。通常最好一开始就避免这种歧义,但如果必须移植大量依赖这种行为的代码,设置variable_conflict可能是最好的解决办法。

  • PostgreSQL中,函数体必须写成字符串字面量。因此你需要使用美元符引用或者转义函数体中的单引号(见第 39.11.1 节)。

  • 应该用模式把函数组织成不同的分组,而不是用包。

  • 因为没有包,所以也没有包级变量。这一点有时会带来不便。你可以改为在临时表中保存会话级状态。

  • 带有REVERSE的整数FOR循环的工作方式不同:PL/SQL中是从第二个数向第一个数倒数,而PL/pgSQL是从第一个数向第二个数倒数,因此在移植时需要交换循环边界。不幸的是这种不兼容性是不太可能改变的(见第 39.6.3.5 节)。

  • 查询上的FOR循环(不是游标)的工作方式同样不同:目标变量必须已经被声明,而PL/SQL总是会隐式地声明它们。但是这样做的优点是在退出循环后,变量值仍然可以访问。

  • 应该用模式把函数组织成不同的分组,而不是用包。

39.12.1. 移植示例

例 39.7展示了如何从PL/SQL移植一个简单的函数到PL/pgSQL中。

例 39.7. 把一个简单函数从 PL/SQL 移植到 PL/pgSQL

这里有一个Oracle PL/SQL函数:

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
                                                  v_version varchar)
RETURN varchar IS
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
/
show errors;

让我们过一遍这个函数并且看看与PL/pgSQL相比有什么样的不同:

  • 在函数原型中(不是函数体中)的RETURN关键字在PostgreSQL中变成了RETURNS。还有,IS变成了AS,并且你还需要增加一个LANGUAGE子句,因为PL/pgSQL并非唯一可用的函数语言。

  • PostgreSQL中,函数体被认为是一个字符串字面量,所以你需要使用引号或者美元引用定界符包围它。这代替了Oracle 方法中的用于终止的/

  • PostgreSQL中没有show errors命令, 并且也不需要这个命令,因为错误是自动报告的。

这个函数被移植到PostgreSQL后看起来会是这样:

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name varchar,
                                                  v_version varchar)
RETURNS varchar AS $$
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
$$ LANGUAGE plpgsql;

例 39.8展示了如何移植一个会创建另一个函数的函数,以及如何处理引号问题。

例 39.8. 把一个创建其它函数的函数从 PL/SQL 移植到 PL/pgSQL

下面的过程从 SELECT 语句读取行,并将结果写入 IF 语句,从而构造一个大型函数,以提高效率。

这是 Oracle 版本:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
    CURSOR referrer_keys IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_cmd VARCHAR(4000);
BEGIN
    func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR,
                 v_domain IN VARCHAR, v_url IN VARCHAR) RETURN VARCHAR IS BEGIN';
                 
    FOR referrer_key IN referrer_keys LOOP
        func_cmd := func_cmd ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ''' || referrer_key.key_string
          || ''' THEN RETURN ''' || referrer_key.referrer_type
          || '''; END IF;';
    END LOOP;

    func_cmd := func_cmd || ' RETURN NULL; END;';

    EXECUTE IMMEDIATE func_cmd;
END;
/
show errors;

下面是这个函数的最终移植结果,目标数据库为PostgreSQL

CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
DECLARE
    referrer_keys CURSOR IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_body text;
    func_cmd text;
BEGIN
    func_body := 'BEGIN';

    FOR referrer_key IN referrer_keys LOOP
        func_body := func_body ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ' || quote_literal(referrer_key.key_string)
          || ' THEN RETURN ' || quote_literal(referrer_key.referrer_type)
          || '; END IF;' ;
    END LOOP;

    func_body := func_body || ' RETURN NULL; END;';

    func_cmd :=
      'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host varchar,
                                                        v_domain varchar,
                                                        v_url varchar)
        RETURNS varchar AS '
      || quote_literal(func_body)
      || ' LANGUAGE plpgsql;' ;

    EXECUTE func_cmd;
END;
$func$ LANGUAGE plpgsql;

注意,这里单独构造函数体,然后将其传给quote_literal,使其中的每个引号都变成两个。这种技术是必需的,因为不能安全地使用美元引用来定义新函数:我们无法确定会插入什么字符串,其来源是referrer_key.key_string字段。(这里假定referrer_key.kind可信,其值总是hostdomainurl,但是referrer_key.key_string可能是任何内容,尤其可能包含美元符号。)这个函数实际上改进了 Oracle 原版:当referrer_key.key_stringreferrer_key.referrer_type中包含引号时,它也不会生成有问题的代码。


例 39.9展示了如何移植一个带有OUT参数和字符串处理的函数。PostgreSQL没有内置的instr函数,但是你可以用其它函数的组合来创建一个。第 39.12.3 节中有一个instrPL/pgSQL实现,你可以用它让你的移植变得更容易。

例 39.9. 把一个带字符串处理和 OUT 参数的过程从 PL/SQL 移植到 PL/pgSQL

下面的Oracle PL/SQL 过程被用来解析一个 URL 并且返回一些元素(主机、路径和查询)。

这是 Oracle 版本:

CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
    CURSOR referrer_keys IS
        SELECT * FROM cs_referrer_keys
        ORDER BY try_order;
    func_cmd VARCHAR(4000);
BEGIN
    func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR,
                 v_domain IN VARCHAR, v_url IN VARCHAR) RETURN VARCHAR IS BEGIN';
                 
    FOR referrer_key IN referrer_keys LOOP
        func_cmd := func_cmd ||
          ' IF v_' || referrer_key.kind
          || ' LIKE ''' || referrer_key.key_string
          || ''' THEN RETURN ''' || referrer_key.referrer_type
          || '''; END IF;';
    END LOOP;

    func_cmd := func_cmd || ' RETURN NULL; END;';

    EXECUTE IMMEDIATE func_cmd;
END;
/
show errors;

这里给出一种可能的 PL/pgSQL 写法:

CREATE OR REPLACE FUNCTION cs_parse_url(
    v_url IN VARCHAR,
    v_host OUT VARCHAR,  -- 这个值将被返回
    v_path OUT VARCHAR,  -- 这个也是
    v_query OUT VARCHAR) -- 还有这个
AS $$
DECLARE
    a_pos1 INTEGER;
    a_pos2 INTEGER;
BEGIN
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//');

    IF a_pos1 = 0 THEN
        RETURN;
    END IF;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    IF a_pos2 = 0 THEN
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        RETURN;
    END IF;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    IF a_pos1 = 0 THEN
        v_path := substr(v_url, a_pos2);
        RETURN;
    END IF;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
END;
$$ LANGUAGE plpgsql;

这个函数可以这样使用:

SELECT * FROM cs_parse_url('http://foobar.com/query.cgi?baz');

例 39.10展示了如何移植一个使用了多种 Oracle 专属特性的过程。

例 39.10. 把一个过程从 PL/SQL 移植到 PL/pgSQL

Oracle 版本:

CREATE OR REPLACE PROCEDURE cs_create_job(v_job_id IN INTEGER) IS
    a_running_job_count INTEGER;
    PRAGMA AUTONOMOUS_TRANSACTION;(1)
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;(2)

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        COMMIT; -- free lock(3)
        raise_application_error(-20000,
                 'Unable to create a new job: a job is currently running.');
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN dup_val_on_index THEN NULL; -- don't worry if it already exists
    END;
    COMMIT;
END;
/
show errors

这样的过程可以很容易地转换为PostgreSQL中返回void的函数。这个过程尤其值得关注,因为它能说明以下几点:

(1)

PostgreSQL中没有PRAGMA语句。

(2)

如果在PL/pgSQL中执行LOCK TABLE,锁要等到调用事务结束时才会释放。

(3)

不能在PL/pgSQL函数中发出COMMIT。函数运行在某个外层事务之中,因此COMMIT意味着终止函数的执行。不过,在这个特定情形中本来也不需要提交,因为抛出错误时,LOCK TABLE获得的锁就会被释放。

下面展示了如何移植这个过程,目标语言为PL/pgSQL

CREATE OR REPLACE FUNCTION cs_create_job(v_job_id integer) RETURNS void AS $$
DECLARE
    a_running_job_count integer;
BEGIN
    LOCK TABLE cs_jobs IN EXCLUSIVE MODE;

    SELECT count(*) INTO a_running_job_count FROM cs_jobs WHERE end_stamp IS NULL;

    IF a_running_job_count > 0 THEN
        RAISE EXCEPTION 'Unable to create a new job: a job is currently running';(1)
    END IF;

    DELETE FROM cs_active_job;
    INSERT INTO cs_active_job(job_id) VALUES (v_job_id);

    BEGIN
        INSERT INTO cs_jobs (job_id, start_stamp) VALUES (v_job_id, now());
    EXCEPTION
        WHEN unique_violation THEN (2)
            -- don't worry if it already exists
    END;
END;
$$ LANGUAGE plpgsql;

(1)

RAISE的语法与 Oracle 的语句相当不同,尽管基本的形式RAISE exception_name工作起来是相似的。

(2)

PL/pgSQL所支持的异常名称不同于 Oracle。内置的异常名称集合要更大(见附录 A)。目前没有办法声明用户定义的异常名称,尽管你能够抛出用户选择的 SQLSTATE 值。

The main functional difference between this procedure and the Oracle equivalent is that the exclusive lock on the cs_jobs table will be held until the calling transaction completes. Also, if the caller later aborts (for example due to an error), the effects of this procedure will be rolled back.


39.12.2. 其他要关注的事项 #

这一节解释了在移植 Oracle PL/SQL函数到PostgreSQL中时要关注的一些其他问题。

39.12.2.1. 异常后隐式回滚 #

PL/pgSQL中,当异常被EXCEPTION子句捕获后,自该块的BEGIN以来所做的所有数据库更改都会被自动回滚。也就是说,这种行为等效于你在 Oracle 中使用下面的代码所得到的效果:

BEGIN
    SAVEPOINT s1;
    ... code here ...
EXCEPTION
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
    WHEN ... THEN
        ROLLBACK TO s1;
        ... code here ...
END;

如果你正在移植一个以这种方式使用SAVEPOINTROLLBACK TO的 Oracle 过程,工作会比较简单:只要省略SAVEPOINTROLLBACK TO即可。如果你的 Oracle 过程以不同方式使用SAVEPOINTROLLBACK TO,那就需要认真思考了。

39.12.2.2. EXECUTE

PL/pgSQL中的EXECUTEPL/SQL中的版本工作方式相似,但必须记得按照第 39.5.4 节中的说明使用quote_literalquote_ident。像 EXECUTE 'SELECT * FROM $1'; 这样的写法如果不借助这些函数,将无法可靠地工作。

39.12.2.3. 优化 PL/pgSQL 函数 #

PostgreSQL提供了两种函数创建修饰符来优化执行:volatility(对于给定的相同参数,函数是否总是返回相同的结果)以及strictness (如果任何参数为空值,函数是否返回空值)。详见CREATE FUNCTION参考页。

在利用这些优化属性时,你的CREATE FUNCTION语句可能像这样:

CREATE FUNCTION foo(...) RETURNS integer AS $$
...
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

39.12.3. 附录 #

这一节包含了一组 Oracle 兼容的instr函数代码,你可以用它来简化你的移植工作。

--
-- instr functions that mimic Oracle's counterpart
-- Syntax: instr(string1, string2, [n], [m]) where [] denotes optional parameters.
--
-- Searches string1 beginning at the nth character for the mth occurrence
-- of string2.  If n is negative, search backwards.  If m is not passed,
-- assume 1 (search starts at first character).
--

CREATE FUNCTION instr(varchar, varchar) RETURNS integer AS $$
DECLARE
    pos integer;
BEGIN
    pos:= instr($1, $2, 1);
    RETURN pos;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;


CREATE FUNCTION instr(string varchar, string_to_search varchar, beg_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN
        temp_str := substring(string FROM beg_index);
        pos := position(string_to_search IN temp_str);

        IF pos = 0 THEN
            RETURN 0;
        ELSE
            RETURN pos + beg_index - 1;
        END IF;
    ELSE
        ss_length := char_length(string_to_search);
        length := char_length(string);
        beg := length + beg_index - ss_length + 2;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            pos := position(string_to_search IN temp_str);

            IF pos > 0 THEN
                RETURN beg;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;


CREATE FUNCTION instr(string varchar, string_to_search varchar,
                      beg_index integer, occur_index integer)
RETURNS integer AS $$
DECLARE
    pos integer NOT NULL DEFAULT 0;
    occur_number integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    i integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN
        beg := beg_index;
        temp_str := substring(string FROM beg_index);

        FOR i IN 1..occur_index LOOP
            pos := position(string_to_search IN temp_str);

            IF i = 1 THEN
                beg := beg + pos - 1;
            ELSE
                beg := beg + pos;
            END IF;

            temp_str := substring(string FROM beg + 1);
        END LOOP;

        IF pos = 0 THEN
            RETURN 0;
        ELSE
            RETURN beg;
        END IF;
    ELSE
        ss_length := char_length(string_to_search);
        length := char_length(string);
        beg := length + beg_index - ss_length + 2;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            pos := position(string_to_search IN temp_str);

            IF pos > 0 THEN
                occur_number := occur_number + 1;

                IF occur_number = occur_index THEN
                    RETURN beg;
                END IF;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql STRICT IMMUTABLE;

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。