SQL存储过程编程笔记


1、引言

最近写了简单的存储过程用于统计数据。这里做个笔记,将此次存储过程用到的一些基础知识点做个简单的小结。(注意:这里数据库用的是postgresql)


2、背景

表1:第三方服务每10分钟会写一次数据到表1,每次写入都是在表中插入一条新的数据,因此一个设备一天在表中有多条数据。

CREATE TABLE tb_check (
	id serial NOT NULL, 			--自增ID
	devicecode text NULL, 			--设备ID 
	dateid int4 NULL,				--日期,格式: 20220509 
	num int4 NULL DEFAULT 0,		--准确数量
	totalnum int4 NULL DEFAULT 0 	--检测总数
);


表2:把表1中的数据进行统计,按 “设备+日期 ”为键值进行存储,即一个设备一天只在表中保存一条数据,并计算出检测准确率。

create table if not exists tb_check_statistics(
	id serial NOT NULL,				--自增ID
	devicecode text NULL,			--设备ID 
	dateid int4 NULL,				--日期,格式: 20220509 
	num int4 NULL DEFAULT 0,		--准确数量
	totalnum int4 NULL DEFAULT 0,	--检测总数
	result text NULL,				--准确率(准确数量/检测总数,格式:0.7500)
	jczt int4 NULL 					--1:正常;2:异常(大于等于75%为正常,否则为异常)
);

目标数据图:
在这里插入图片描述

3、 创建存储过程基本格式

drop function if exists testprocess;
CREATE OR REPLACE FUNCTION testprocess()
RETURNS INTEGER AS
$BODY$
	declare 
		--声明变量
		
	begin 
		--操作模块
		RAISE NOTICE 'this is a test process, date:%', 20220509;
		return 0; --这里要和第二行定义的返回值对应,否则会报错
	end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

执行:select * from testprocess();,输出结果如下图
在这里插入图片描述


4、 结构体定义

数据结构的定义与我们平时编程相似,如下我定义了两个数据结构,一个用于存储表字段数据,一个用于存储时间:

--表字段结构体
CREATE TYPE check_type AS (
    devicecode     	text,
    dateid          INTEGER,
	num          	INTEGER,
	totalnum        INTEGER,
	result     		text,      	
	jczt     		INTEGER		 	
);

--时间结构体
CREATE TYPE time_item AS (
    currentdate text, 	--'2022-04-06 13:37:17'
	currentyear text,	--'2022'
	currentmonth text,	--'04'
	currentday text,	--'06'
	timestr text,		--'20220406'
	timeid INTEGER		--20220406
);


5、 声明变量

变量类型包括:表、结构体、数组、整形、布尔、字符等。

declare 
		data_list11 tb_check; 		--为表1声明一个变量,用于读取表数据时提取参数值
		timeitem time_item; 		--时间结构体
		ivitem check_type;			--表字段结构体
		ivitemarray  check_type[];	--表字段数组
		countid INTEGER := 0;		
		iindex INTEGER := 0;
		isflag boolean := false;
		radius NUMERIC := 1.0;
		caluresult NUMERIC := 1.0;
		datetime NUMERIC := 0;


6、 系统时间获取和格式转换


1. 获取系统时间
获取当前时间,并使用select…into…赋给变量

select floor(extract(epoch from now())) into datetime;	--单位:秒


2. 时间格式转换

timeitem time_item;				--定义结构体变量
select to_char(to_timestamp(datetime), 'yyyy-mm-dd hh24:mi:ss') into timeitem.currentdate; --'2022-04-06 13:37:17' 
timeitem.currentdate = substring(timeitem.currentdate from 1 for 10); 		--'2022-04-06'
select split_part(timeitem.currentdate ,'-', 1) into timeitem.currentyear; 	--'2022'
select split_part(timeitem.currentdate ,'-', 2) into timeitem.currentmonth;	--'04'
select split_part(timeitem.currentdate ,'-', 3) into timeitem.currentday;	--'06'
select timeitem.currentyear || timeitem.currentmonth || timeitem.currentday into timeitem.timestr; -- '20220406'
select timeitem.timestr::INTEGER into timeitem.timeid; --20220406


7、 读取表 tb_check 数据

循环读取表tb_check 数据,并存放到数组中。

--以天为条件循环读取一天的数据, 提取数据并存储到数组中
for data_list11 in select * from tb_check  where dateid=timeitem.timeid loop 
	--1)循环遍历数组ivitemarray,以devicecode为唯一标识,查看数组中是否已经存在该设备的值,如果存在则更新;
	isflag := false;
	if countid > 0 then
		FOR n IN 1..countid LOOP --循环数组 
			if ivitemarray[n].devicecode = data_list11.devicecode then --数组中可以查到,则更新数据
				ivitem.devicecode = data_list11.devicecode;
				ivitem.dateid = data_list11.dateid;
				ivitem.num = ivitemarray[n].num+data_list11.num;
				ivitem.totalnum = ivitemarray[n].totalnum+data_list11.totalnum;
				ivitemarray[n] = ivitem; 			--覆盖原值

				isflag := true;
				EXIT;					--找到则退出循环
			end if;
		END LOOP;	
	end if;
	
	--2)若不存在则添加到数组	
	if isflag = false then --数组中查询不到,则添加到数组
		ivitem.devicecode = data_list11.devicecode;
		ivitem.dateid = data_list11.dateid;
		ivitem.num = data_list11.num;
		ivitem.totalnum = data_list11.totalnum;
		countid := countid+1;
		ivitemarray[countid] = ivitem;	
	end if;
end loop; 


8、 数据插入到表 tb_check_statistics

遍历数组,把数据插入到表tb_check_statistics。

if countid > 0 then
	FOR n IN 1..countid LOOP
		--准确率计算
		--这里省略,不做说明........
		
		--插入表中
		insert into tb_check_statistics
		(
			devicecode, dateid, num, totalnum, result, jczt
		)values
		(
			ivitemarray[n].devicecode, ivitemarray[n].dateid, ivitemarray[n].num, 
			ivitemarray[n].totalnum, ivitemarray[n].result, ivitemarray[n].jczt
		);
		
	END LOOP;
end if;


9、 插入数据:若已存在则更新否则插入

题外话需求:有一张表,要求插入数据的时候,如果已经存在则更新,不存在则插入。
mysql 和 postgresql 在实现上有所不同,这里简单介绍下 postgresql 的 ON CONFLICT 使用方法。


1、 使用ON CONFLICT 对表有特别的要求,就是表需要指定 ‘约束唯一标识

create table if not exists tb_check_ex(
	id serial NOT NULL,
	devicecode text NULL,
	dateid int4 NULL,
	t11 int4 NULL DEFAULT 0,		
	t12 int4 NULL DEFAULT 0,		
	t13 text NULL,					
	t14 int4 NULL, 					
	t21 int4 NULL DEFAULT 0,		
	t22 int4 NULL DEFAULT 0,		
	t23 text NULL,					
	t24 int4 NULL, 					
	unique(dateid,devicecode) --需要特别指定 约束唯一标识
);

约束唯一标识,可以是单个字段,也可以是多个字段组合,这里采用 ‘dateid,devicecode’两个字段作为约束唯一标识。



2、 插入/更新 语句

--insert t11, t12, t13, t14
insert into tb_check_ex
(
	devicecode, dateid, t11, t12, t13, t14
)values
(
	ivitemarray[n].devicecode, ivitemarray[n].dateid, ivitemarray[n].num, 
	ivitemarray[n].totalnum, ivitemarray[n].result, ivitemarray[n].jczt
)ON CONFLICT (devicecode,dateid) 
DO UPDATE SET t11=ivitemarray[n].num,t12=ivitemarray[n].totalnum,t13=ivitemarray[n].result,t14=ivitemarray[n].jczt;

--insert t21, t22, t23, t24
insert into tb_check_ex
(
	devicecode, dateid, t21, t22, t23, t24
)values
(
	ivitemarray[n].devicecode, ivitemarray[n].dateid, ivitemarray[n].num, 
	ivitemarray[n].totalnum, ivitemarray[n].result, ivitemarray[n].jczt
)ON CONFLICT (devicecode,dateid) 
DO UPDATE SET t21=ivitemarray[n].num,t22=ivitemarray[n].totalnum,t23=ivitemarray[n].result,t24=ivitemarray[n].jczt;

如此,在 devicecode,dateid 相同的情况下,上述两个插入语句执行完成后,在表中只保存了一条数据。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/locahuang/article/details/124669902
今日推荐