工作中可能会存在业务比较复杂,重复性工作比较多,需要批量处理数据的情况,此时使用存储过程会方便很多,存储过程的执行效率也会快很多,能帮助我们节省很多代码和时间。
并且,将需要的sql写成存储过程并设置成定时任务,那样在任意时刻,需要执行任意次数都可以根据你的设定执行,哪怕你不在工位上,减少你的工作量,能让你更愉快的摸鱼(不是)。
CREATE [ OR REPLACE ] FUNCTION
name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
[ RETURNS rettype
| RETURNS TABLE ( column_name column_type [, ...] ) ]
{ LANGUAGE lang_name
| TRANSFORM { FOR TYPE type_name } [, ... ]
| WINDOW
| IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
| PARALLEL { UNSAFE | RESTRICTED | SAFE }
| COST execution_cost
| ROWS result_rows
| SUPPORT support_function
| SET configuration_parameter { TO value | = value | FROM CURRENT }
| AS 'definition'
| AS 'obj_file', 'link_symbol'
} ...
由官方文档得到的定义一个函数的语法,当然现实中不需要所有的要素都要定义到。现在就常用的要素做出解释。
注意:定义函数的时候,参数可以是空,但是哪怕不修改函数体只修改参数,它会得到一个新的函数。
[ <
PL/pgSQL是一种块结构的语言。一个函数体的完整文本必须是一个块。存储过程的语法如上所示。
在一个块中的每一个声明和每一个语句都由一个分号终止。
所有的关键词都是大小写无关的。除非被双引号引用,标识符会被隐式地转换为小写形式,就像它们在普通 SQL 命令中。
PL/pgSQL代码中的注释和普通 SQL 中的一样。一个双连字符(–)开始一段注释,它延伸到该行的末尾。一个/* 开始一段块注释,它会延伸到匹配*/出现的位置。块注释可以嵌套。
name [ CONSTANT ] type [ COLLATE collation_name ] [ NOT NULL ] [ { DEFAULT | := | = } expression ];
在自定义函数中声明一个变量,并给这个变量赋值的时候可以用这个方法。示例如下:
-- 1
declare a integer default 32;
-- 2
declare a integer :=32;
-- 3
declare a integer;
a :=32;
这三种方法都能将声明一个变量a,并且将32赋值给a。若不给a赋值,就是方法三中没有a:=32;也不会报错,就是变量a初始化为sql空值。
-- 报错1
-- 加了constant,已经无法修改a的值了。
declare a constant integer default 32;
a :=1;
-- 报错2
-- 在声明变量的时候选择了not null,就应该在声明时赋值,否则哪怕后面赋值还是会报错
declare a integer not null;
a :=32;
方式一:into子句
SELECT select_expressions INTO [STRICT] target FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] target;
UPDATE ... RETURNING expressions INTO [STRICT] target;
DELETE ... RETURNING expressions INTO [STRICT] target;
举个例子,如下:
-- 这就表示把test表中的id字段的值赋值给a
-- 其中 select id from test就是基础sql命令从test表中查询id的值
-- 通过 into a 将查询得到的值赋值给a
select id into a from test;
方式二:动态命令
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];
举个例子,如下:
-- 和上面一样,把id的值查询出来赋值给a
execute 'select id from test' into a;
returns返回类型可以是一种基本类型、组合类型或者域类型,也可以引用一个表列的类型。
当有OUT或者INOUT参数时,可以省略RETURNS子句。如果存在,该子句必须和输出参数所表示的结果类型一致:如果有多个输出参数,则为RECORD,否则与单个输出参数的类型相同。
如果该函数不会返回一个值,可以指定返回类型为void。
如果选择返回returns void,那函数体最后就不用return了。
CREATE OR REPLACE FUNCTION "public"."func1"()
RETURNS "pg_catalog"."void" AS $BODY$ -- returns void,在保存的时候自动会变成"pg_catalog"."void"
BEGIN
-- 建表语句
create table a(
id int4,
name varchar(50)
);
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
返回的结果是类似int4,float,text等这些基本数据类型都可以。
示例:
CREATE OR REPLACE FUNCTION "public"."func1"()
RETURNS "pg_catalog"."text" AS $BODY$
declare val text;
BEGIN
insert into a values (1,'小明') returning name into val;
return val;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
这个函数的结果会返回val的值,如下图:
并且在表a中会插入一条数据,如下图。
这里敲黑板!!! 这里通过into子句赋值给变量,返回的是结果的第一行或者null(查询返回零行),除非使用order by进行排序,否则第一行是不明确的,第一行之后所有的结果都会被丢弃。
如果加了strict选项,那么查询结果必须是恰好一行,否则就会报错。
举个例子,现在在a表中插入数据,表a数据如下。
然后从表中查询出name值赋值val。
CREATE OR REPLACE FUNCTION "public"."func1"()
RETURNS "pg_catalog"."text" AS $BODY$
declare val text;
BEGIN
select name into val from a ;
return val;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
得到的结果如下显示。返回一行数据,并且这个第一行的排序并不是表的第一行数据。
若是添加strict选项,结果返回的是多条数据就会报错,如下显示。
如果返回的结果刚好是一行数据的,则添加strict选项就可以显示最终结果。
正常显示结果并且返回。结果如下:
到目前为止,现在返回的结果要不是返回空要不就是返回一个记录,若是想要多条数据该怎么处理呢?
CREATE OR REPLACE FUNCTION "public"."func1"()
RETURNS SETOF "public"."a" AS $BODY$
BEGIN
return query select a.id,a.name from a limit 2;
return;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100
ROWS 1000
得到的结果如下图。
期中return query还可以使用return next。具体语法是:
RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];
详细情况可以看查看官方文档。
使用方法可以参考(https://www.cnblogs.com/yanshw/p/12120473.html)
以及参考(https://my.oschina.net/Kenyon/blog/108303)
postgresql中可以使用的控制结构,有条件结构和循环结构。
IF语句:
CREATE OR REPLACE FUNCTION "public"."func1"("a" int4)
RETURNS "pg_catalog"."text" AS $BODY$
declare rel varchar;
BEGIN
IF a>=90 THEN
rel:='优秀';
elsif a>=80 then
rel := '良好';
elsif a>=60 then
rel := '及格';
ELSE
rel :='不及格';
END IF;
return rel;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100
CASE语句:
CREATE OR REPLACE FUNCTION "public"."func1"("a" int4)
RETURNS "pg_catalog"."text" AS $BODY$
declare rel varchar;
BEGIN
case when a>=90 THEN
rel:='优秀';
when a>=80 then
rel := '良好';
when a>=60 then
rel := '及格';
ELSE
rel :='不及格';
END case;
return rel;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100
循环结构有loop,exit,continue,while,for和foreach语句
[ <
loop定义的是一个无条件循环,会无限重复直到被exit或return语句终止,所以
EXIT [ label ] [ WHEN boolean-expression ];
指定when,当boolean-expression为真时会退出循环。配合loop给个例子。
LOOP
raise notice 'a is %',a;
a :=a-1;
IF a<=rel THEN
EXIT;
END IF;
END LOOP;
-- 等同于
LOOP
raise notice 'a is %',a;
a :=a-1;
EXIT when a<=rel; -- 这个相当于前面整个if判断
END LOOP;
CONTINUE [ label ] [ WHEN boolean-expression ];
[ <
当boolean-expression为真的时候,这个循环会执行。举例:
CREATE OR REPLACE FUNCTION "public"."loops"("a" int4)
RETURNS "pg_catalog"."void" AS $BODY$
declare rel integer default 0;
BEGIN
WHILE a>0 LOOP
raise info 'a= %',a;
rel := rel+a;
a:=a-1;
END LOOP;
raise info 'rel = %',rel;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
[ <
for循环会创建一个整数返回进行迭代。此时下界临界值小于上界临界值,若是制定reverse,则上界临界值写在前,下界临界值写在后,默认步长为1,若是正向迭代,每次迭代数值都是加1,若是反向迭代都是减1。通过by,可以指定步长。
例如:
FOR i IN 1..10 LOOP
-- 我在循环中将取值 1,2,3,4,5,6,7,8,9,10
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- 我在循环中将取值 10,9,8,7,6,5,4,3,2,1
END LOOP;
FOR i IN REVERSE 10..1 BY 2 LOOP
-- 我在循环中将取值 10,8,6,4,2
END LOOP;
查询结果循环
[ <
target是一个记录变量、行变量或者逗号分隔的标量变量列表。target被连续不断被赋予来自query的每一行,并且循环体将为每一行执行一次。下面是一个例子:
CREATE OR REPLACE FUNCTION "public"."loops"()
RETURNS "pg_catalog"."void" AS $BODY$
declare rel record;
BEGIN
FOR rel IN select id,name from a LOOP
-- quote_ident()的作用是为字符串加上双引号
raise notice 'a表中的用户信息为id: %,name: %',rel.id,quote_ident(rel.name);
END LOOP;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
结果显示如下:
for-in-execute语句在行上迭代的另一种方式:
[ <
这个例子类似前面的形式,只不过源查询被指定为一个字符串表达式,在每次进入FOR循环时都会计算它并且重新规划。
在使用EXECUTE时,可以通过USING将参数值插入到动态命令中。
[ <
具体例子不再给出,看看官方文档中的例子。
CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
s int8 := 0;
x int;
BEGIN
FOREACH x IN ARRAY $1
LOOP
s := s + x;
END LOOP;
RETURN s;
END;
$$ LANGUAGE plpgsql;
使用raise语句报告消息以及抛出错误,上面给的示例中已经有部分给出,语法是
RAISE [ level ] 'format' [, expression [, ... ]] [ USING option = expression [, ... ] ];
RAISE [ level ] condition_name [ USING option = expression [, ... ] ];
RAISE [ level ] SQLSTATE 'sqlstate' [ USING option = expression [, ... ] ];
RAISE [ level ] USING option = expression [, ... ];
RAISE ;
level选项指定了错误的严重性。允许的级别有DEBUG、LOG、INFO、NOTICE, WARNING以及EXCEPTION,默认级别是EXCEPTION。
raise log ‘这是日志消息’; – 输出在日志文件中
raise inof ‘这是一个信息’; – 以下信息打印在控制台
raise notice ‘这个是提示消息’;
raise notice warning ‘这是个警告’;
raise exception ‘这个异常消息’;
当存储过程编译出来后,我们该如何执行或者调用存储过程呢?语法如下。
select function_name();
select * from function_name();
select * from function_name where 筛选条件; -- 当返回的结果为多条数据的结果集的时候
select * from function_name() as tablename(column name,column type[,...]) ; --动态返回结果集
以上就是存储过程的基本语法,接下来再给几个具体实例巩固一下。
案例一:无查询结果时,不用select,用perform。
CREATE OR REPLACE FUNCTION "public"."fun_etc"()
RETURNS "pg_catalog"."void" AS $BODY$
BEGIN
perform current_date;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
案例二:for查询结果循环,声明语法拷贝类型(拷贝一个行变量)
拷贝行类型,语法声明:v_value table_name%ROWTYPE
若是拷贝一个已有的列数据类型,语法:v_value variable%TYPE,类似declare b a.name%TYPE
CREATE OR REPLACE FUNCTION "public"."fun_etc"()
RETURNS "pg_catalog"."text" AS $BODY$
declare b a%rowtype;
BEGIN
for b in select id,name from a loop
raise info 'b的值=%',b;
end loop;
return b.id||'---'||b.name;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
信息结果:
return返回结果:return返回只会返回执行的最后一个结果。
案例三:当sql中的语句有变量的时候,可以用||拼接,最后execute执行。
CREATE OR REPLACE FUNCTION "public"."fun_etc"()
RETURNS "pg_catalog"."void" AS $BODY$
declare ifexists integer;
declare sqltext text;
declare b record;
BEGIN
-- 判断表是否存在,存在值=1,不存在值=0
sqltext:='
select count(1) from pg_class where relname=''a_'||to_char(CURRENT_DATE,'yyyy_mm_dd')||'''';
execute sqltext into ifexists;
-- 判断表是否存在,不存在则建表
IF ifexists=0 then
-- 建立一张新表
sqltext:='
create table "a_'||to_char(CURRENT_DATE,'yyyy_mm_dd')||'"
(
create_time date,
id int4,
name varchar(50)
);';
execute sqltext;
END IF;
-- 从表a中查询数据插入这表中
sqltext :='
insert into a_'||to_char(CURRENT_DATE,'yyyy_mm_dd')||'
select CURRENT_DATE,id,name from a
';
execute sqltext;
-- 查询显示数据
sqltext:= 'select create_time,id,name from a_'||to_char(CURRENT_DATE,'yyyy_mm_dd')||';';
for b in execute sqltext loop
raise info '%',b;
end loop;
return ;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
案例四:为了使函数更加通用,以解决动态返回数据集的问题,将表名作为参数传进去。
CREATE OR REPLACE FUNCTION "public"."fun_etc"("name" varchar)
RETURNS SETOF "pg_catalog"."record" AS $BODY$
declare b record;
BEGIN
for b in execute 'select * from '||name loop
return next b;
end loop;
return ;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000
select * from fun_etc('a') as a(id int,name varchar(50));
select * from fun_etc('a_2022_12_21') as a(create_time date,id int,name varchar(50));
结果显示:
其他需要注意的是,我这边都是在一个函数上修改,若是参数都一样的话,他是会在原函数上修改的,所以你们别这样啊,否则都白写了。
好了,本文到这里就结束了。
感觉您的阅读~