七月 17, 2023
PL/SQL是Oracle内置的数据库编程语言,提供了几种方法来存储和运行数据库中可重用的业务逻辑。过程和函数是使用CREATE PROCEDURE
和CREATE FUNCTION
语句创建的可重用代码片段。
Oracle 用法
存储过程和存储函数是由 SQL 和 PL/SQL 语句组成的 PL/SQL 代码单元,用于解决特定问题或执行一组相关任务。
过程用于使用 PL/SQL 执行数据库操作。
函数用于执行计算并返回结果。
创建过程和函数的权限
若要在自己的架构中创建过程和函数,Oracle 数据库用户需要CREATE PROCEDURE
系统权限。
若要在其他架构中创建过程或函数,数据库用户需要CREATE ANY PROCEDURE
权限。
要运行过程或函数,数据库用户需要EXECUTE
权限。
包规格和包体
除了存储过程和函数之外,Oracle 还提供包来封装相关的过程、函数和其他程序对象。
包规格声明并描述了所有相关的PL/SQL元素。
包体包含可执行代码。
若要运行在包中创建的存储过程或函数,请指定包名称和存储过程或函数名称。
EXEC PKG_EMP.CALCULTE_SAL('100');
例子
使用CREATE OR REPLACE PROCEDURE
语句创建 Oracle 存储过程。可选的OR REPLACE
子句将覆盖具有相同名称的现有存储过程(如果存在)。
CREATE OR REPLACE PROCEDURE EMP_SAL_RAISE
(P_EMP_ID IN NUMBER, SAL_RAISE IN NUMBER)
AS
V_EMP_CURRENT_SAL NUMBER;
BEGIN
SELECT SALARY INTO V_EMP_CURRENT_SAL
FROM EMPLOYEES WHERE EMPLOYEE_ID=P_EMP_ID;
UPDATE EMPLOYEES
SET SALARY=V_EMP_CURRENT_SAL+SAL_RAISE
WHERE EMPLOYEE_ID=P_EMP_ID;
DBMS_OUTPUT.PUT_LINE('New Salary For Employee ID: ' || P_EMP_ID ||
' Is ' || (V_EMP_CURRENT_SAL + SAL_RAISE));
EXCEPTION WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, 'An error was encountered - ' ||
SQLCODE || ' -ERROR-' || SQLERRM);
ROLLBACK;
COMMIT;
END;
/
-- Run
EXEC EMP_SAL_RAISE(200, 1000);
使用CREATE OR REPLACE FUNCTION
语句创建一个函数。
CREATE OR REPLACE FUNCTION EMP_PERIOD_OF_SERVICE_YEAR
(P_EMP_ID NUMBER)
RETURN NUMBER
AS
V_PERIOD_OF_SERVICE_YEARS NUMBER;
BEGIN
SELECT EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM TO_DATE(HIRE_DATE))
INTO V_PERIOD_OF_SERVICE_YEARS
FROM EMPLOYEES
WHERE EMPLOYEE_ID=P_EMP_ID;
RETURN V_PERIOD_OF_SERVICE_YEARS;
END;
/
SELECT EMPLOYEE_ID, FIRST_NAME,
EMP_PERIOD_OF_SERVICE_YEAR(EMPLOYEE_ID) AS PERIOD_OF_SERVICE_YEAR
FROM EMPLOYEES;
EMPLOYEE_ID FIRST_NAME PERIOD_OF_SERVICE_YEAR
174 Ellen 13
166 Sundar 9
130 Mozhe 12
105 David 12
204 Hermann 15
116 Shelli 12
167 Amit 9
172 Elizabeth 10
使用CREATE OR REPLACE PACKAGE
语句创建包。
CREATE OR REPLACE PACKAGE PCK_CHINOOK_REPORTS
AS
PROCEDURE GET_ARTIST_BY_ALBUM(P_ARTIST_ID ALBUM.TITLE%TYPE);
PROCEDURE CUST_INVOICE_BY_YEAR_ANALYZE;
END;
使用CREATE OR REPLACE PACKAGE BODY
语句创建新包。
CREATE OR REPLACE PACKAGE BODY PCK_CHINOOK_REPORTS
AS
PROCEDURE GET_ARTIST_BY_ALBUM(P_ARTIST_ID ALBUM.TITLE%TYPE)
IS
V_ARTIST_NAME ARTIST.NAME%TYPE;
BEGIN
SELECT ART.NAME INTO V_ARTIST_NAME
FROM ALBUM ALB JOIN ARTIST ART USING(ARTISTID)
WHERE ALB.TITLE = P_ARTIST_ID;
DBMS_OUTPUT.PUT_LINE('ArtistName: ' || V_ARTIST_NAME);
END;
PROCEDURE CUST_INVOICE_BY_YEAR_ANALYZE
AS
V_CUST_GENRES VARCHAR2(200);
BEGIN
FOR V IN
(SELECT CUSTOMERID, CUSTNAME, LOW_YEAR, HIGH_YEAR, CUST_AVG
FROM TMP_CUST_INVOICE_ANALYSE)
LOOP
IF SUBSTR(V.LOW_YEAR, -4) > SUBSTR(V.HIGH_YEAR , -4) THEN
SELECT LISTAGG(GENRE, ',') WITHIN GROUP (ORDER BY GENRE)
INTO V_CUST_GENRES FROM (
SELECT DISTINCT FUNC_GENRE_BY_ID(TRC.GENREID) AS GENRE
FROM TMP_CUST_INVOICE_ANALYSE TMPTBL
JOIN INVOICE INV USING(CUSTOMERID)
JOIN INVOICELINE INVLIN ON INV.INVOICEID = INVLIN.INVOICEID
JOIN TRACK TRC ON TRC.TRACKID = INVLIN.TRACKID
WHERE CUSTOMERID=V.CUSTOMERID);
DBMS_OUTPUT.PUT_LINE('Customer: ' || UPPER(V.CUSTNAME) ||
' - Offer a Discount According To Preferred Genres: ' ||
UPPER(V_CUST_GENRES));
END IF;
END LOOP;
END;
END;
EXEC PCK_CHINOOK_REPORTS.GET_ARTIST_BY_ALBUM();
EXEC PCK_CHINOOK_REPORTS.CUST_INVOICE_BY_YEAR_ANALYZE;
前面的示例演示了基本的 Oracle PL/SQL 过程和函数功能。Oracle PL/SQL 提供了大量不在本文档范围内的特性和功能。
有关详细信息,请参阅 Oracle 文档中的 CREATE FUNCTION 和 CREATE PROCEDURE 。
PostgreSQL 用法
PostgreSQL 使用CREATE FUNCTION
语句为存储过程和存储函数提供支持。需要强调的是,PostgreSQL使用的过程语句仅支持CREATE FUNCTION
语句。CREATE PROCEDURE
语句与此 PostgreSQL 版本不兼容。
PL/pgSQL 是用于从Oracle PL/SQL代码迁移的主要数据库编程语言。PostgreSQL 支持的编程语言不限于PL/pgSQL,也支持这些语言:
- PL/pgSQL
- PL/Python
- PL/Perl
Oracle PL/SQL 和 PostgreSQL PL/pgSQL 之间的互换性
PostgreSQL PL/pgSQL语言通常被认为是从Oracle PL/SQL代码迁移的理想候选者,因为PostgreSQL PL/pgSQL代码支持许多Oracle PL/SQL语法元素。
例如,PostgreSQL PL/pgSQL支持Oracle CREATE OR REPLACE PROCEDURE
语句。还支持许多其他PL / SQL语法元素,使PostgreSQL和PL / pgSQL在从Oracle迁移时成为自然的替代方案。
PostgreSQL 创建函数权限
要创建函数,用户必须具有该语言的USAGE
权限。创建函数时,可以指定语言参数,如示例中所示。
例子
将 Oracle 存储过程和函数转换为 PostgreSQL PL/pgSQL。
使用 PostgreSQL 的CREATE FUNCTION
命令创建一个名为FUNC_ALG
的新函数。
CREATE OR REPLACE FUNCTION FUNC_ALG(P_NUM NUMERIC)
RETURNS NUMERIC
AS $$
BEGIN
RETURN P_NUM * 2;
END;$$
LANGUAGE PLPGSQL;
使用CREATE OR REPLACE
语句创建新函数或替换现有函数,但有以下限制:
- 您无法更改函数名称或参数类型。
- 该语句不允许更改现有函数返回类型。
- 用户必须拥有函数才能替换它。
INPUT
参数 (P_NUM
) 的实现方式与 Oracle PL/SQL 的INPUT
参数类似。- 两个美元符号用于防止需要使用单引号字符串转义元素。使用两个美元符号时,使用单引号 ( ’ ) 时无需在代码中使用转义字符。两个美元符号出现在关键字
AS
之后和函数关键字END
之后。 - 使用
LANGUAGE PLPGSQL
参数指定所创建函数的语言。
将Oracle EMP_SAL_RAISE
PL/SQL函数转换为PostgreSQL PL/pgSQL。
CREATE OR REPLACE FUNCTION EMP_SAL_RAISE
(IN P_EMP_ID DOUBLE PRECISION,
IN SAL_RAISE DOUBLE PRECISION)
RETURNS VOID
AS $$
DECLARE
V_EMP_CURRENT_SAL DOUBLE PRECISION;
BEGIN
SELECT SALARY INTO STRICT V_EMP_CURRENT_SAL
FROM EMPLOYEES WHERE EMPLOYEE_ID = P_EMP_ID;
UPDATE EMPLOYEES
SET SALARY = V_EMP_CURRENT_SAL + SAL_RAISE
WHERE EMPLOYEE_ID = P_EMP_ID;
RAISE DEBUG USING MESSAGE := CONCAT_WS('',
'NEW SALARY FOR EMPLOYEE ID: ', P_EMP_ID, 'IS ',
(V_EMP_CURRENT_SAL + SAL_RAISE));
EXCEPTION
WHEN OTHERS THEN
RAISE USING ERRCODE := '20001', MESSAGE :=
CONCAT_WS('', 'AN ERROR WAS ENCOUNTERED - ',
SQLSTATE, ' -ERROR-', SQLERRM);
END;$$
LANGUAGE PLPGSQL;
select emp_sal_raise(200, 1000);
将Oracle EMP_PERIOD_OF_SERVICE_YEAR
PL/SQL函数转换为PostgreSQL PL/pgSQL。
CREATE OR REPLACE FUNCTION
EMP_PERIOD_OF_SERVICE_YEAR (IN P_EMP_ID DOUBLE PRECISION)
RETURNS DOUBLE PRECISION
AS $$
DECLARE
V_PERIOD_OF_SERVICE_YEARS DOUBLE PRECISION;
BEGIN
SELECT
EXTRACT (YEAR FROM NOW()) - EXTRACT (YEAR FROM (HIRE_DATE))
INTO STRICT V_PERIOD_OF_SERVICE_YEARS
FROM EMPLOYEES
WHERE EMPLOYEE_ID = P_EMP_ID;
RETURN V_PERIOD_OF_SERVICE_YEARS;
END;$$
LANGUAGE PLPGSQL;
SELECT EMPLOYEE_ID, FIRST_NAME,
EMP_PERIOD_OF_SERVICE_YEAR(EMPLOYEE_ID) AS PERIOD_OF_SERVICE_YEAR
FROM EMPLOYEES;
Oracle 包规格和包体
PostgreSQL 不支持 Oracle 包对象和包体。所有 PL/SQL 对象都必须转换为 PostgreSQL 函数。以下示例介绍了转换时如何处理 Oracle 包对象和包体名称。
Oracle 包名称:PCK_CHINOOK_REPORTS
。Oracle 包过程:GET_ARTIST_BY_ALBUM
。
EXEC PCK_CHINOOK_REPORTS.GET_ARTIST_BY_ALBUM('');
你可以在转换到 PostgreSQL 代码时,使用符号$
分隔包和过程名称。
SELECT PCK_CHINOOK_REPORTS$GET_ARTIST_BY_ALBUM('');
例子
将 Oracle 包和包体转换为 PostgreSQL PL/pgSQL。
在以下示例中,Oracle 包名称为PCK_CHINOOK_REPORTS
,Oracle 包过程为GET_ARTIST_BY_ALBUM
。
CREATE OR REPLACE FUNCTION
chinook."PCK_CHINOOK_REPORTS$GET_ARTIST_BY_ALBUM"
(p_artist_id text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
V_ARTIST_NAME CHINOOK.ARTIST.NAME%TYPE;
BEGIN
SELECT art.name INTO STRICT V_ARTIST_NAME
FROM chinook.album AS alb
JOIN chinook.artist AS art
USING (artistid)
WHERE alb.title = p_artist_id;
RAISE DEBUG USING MESSAGE := CONCAT_WS('', 'ArtistName: ', V_ARTIST_NAME);
END;
$function$;
-- Procedures (Packages) Verification
set client_min_messages = 'debug';
-- Equivalent to Oracle SET SERVEROUTPUT ON
select chinook.pck_chinook_reports$get_artist_by_album(' Fireball');
在以下示例中,Oracle 包名称为PCK_CHINOOK_REPORTS
,Oracle 包过程为CUST_INVOICE_BY_YEAR_ANALYZE
。
CREATE OR REPLACE FUNCTION chinook."pck_chinook_reports$cust_invoice_by_year_analyze" ()
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_cust_genres CHARACTER VARYING(200);
v RECORD;
BEGIN
FOR v IN
SELECT customerid, custname, low_year, high_year, cust_avg
FROM chinook.tmp_cust_invoice_analyse
LOOP
IF SUBSTR(v.low_year, - 4) > SUBSTR(v.high_year, - 4) THEN
-- Altering Oracle LISTAGG Function With PostgreSQL STRING_AGG Function
select string_agg(genre, ',') into v_cust_genres
from (select distinct chinook.func_genre_by_id(trc.genreid) as genre
from chinook.tmp_cust_invoice_analyse tmptbl
join chinook.INVOICE inv using(customerid)
join chinook.INVOICELINE invlin on inv.invoiceid = invlin.invoiceid
join chinook.TRACK trc on trc.trackid = invlin.trackid
where customerid=v.CUSTOMERID) a;
-- PostgreSQL Equivalent To Oracle DBMS_OUTPUT.PUT_LINE()\
RAISE DEBUG USING MESSAGE := CONCAT_WS('', 'Customer: ',
UPPER(v.custname), ' - Offer a Discount According To Preferred Genres: ',
UPPER(v_cust_genres));
END IF;
END LOOP;
END;
$function$;
-- Running
SELECT chinook.pck_chinook_reports$cust_invoice_by_year_analyze();
PostgreSQL 版本 10 中LATERAL FROM
子句使用的集合返回函数的新行为。
以前
CREATE TABLE emps (id int, manager int);
INSERT INTO tab VALUES (23, 24), (52, 23), (21, 65);
SELECT x, generate_series(1,5) AS g FROM tab;
id |g
---|--
23 |1
23 |2
23 |3
23 |4
23 |5
52 |1
52 |2
52 |3
52 |4
52 |5
21 |1
21 |2
21 |3
21 |4
21 |5
新增功能
SELECT id, g FROM emps, LATERAL generate_series(1,5) AS g;
id |g
---|--
23 |1
23 |2
23 |3
23 |4
23 |5
52 |1
52 |2
52 |3
52 |4
52 |5
21 |1
21 |2
21 |3
21 |4
21 |5
在这里,规划器可以选择将设置-返回函数放在 nestloop 连接的外部,因为它对 emps 表没有实际的横向依赖关系。
有关详细信息,请参阅 PostgreSQL 文档中的 CREATE FUNCTION、PL/pgSQL — SQL过程语言、过程语言和查询语言(SQL)函数。