最近两个整理了门诊药房发药的业务逻辑,准备通过存储过程实现数据处理。耗费两天时间验证终于完成,对存储过程的了解又深入了一些,总结如下:

1、游标的遍历使用了FOR IN语句进行循环,比FETCH与方便了好多,不用声明一堆变量去接收;而且FOR循环使用的游标是自动打开、自动关闭的无需手动关闭。

2、通过跟踪调试发现,游标虽然是在过程开始就声明了,语句执行到FOR循环时才真正打开游标(执行游标定义的SELECT语句)。

3、存储过程中触发一个自定义的异常的语法:RAISE_APPLICATION_ERROR(-20001, '这是一个自定义的异常信息');

只是还有以下两个小疑问目前没有找到答案,那位朋友有明确答案期待在评论区留言告知:

1)、在LOOP中直接RETURN不知道游标是否会自动关闭?所以这里不确定我使用了GOTO语句跳出到标签返回的。

2)、如何实现在存储过程中让一个游标打开一次后,可以进行两次循环?也就是一次数据查询进行两次结果使用,只能使用临时表吗。

CREATE OR REPLACE PROCEDURE P_OPD_SENDDRUG (
--门诊药房发药过程 liuyc 20210928
receiveId IN INT, --收款单主表id
operateId IN INT, --操作员id
deptId IN INT, --执行发药药房id
mess OUT VARCHAR2, --OK为执行成功
sendId OUT INT, --生成发药记录主表id
priceHint OUT VARCHAR2, --低于进价发药记录的提示信息
needPrint OUT INT --返回1需要打印,0不需要打印
)
IS
--本组变量保存收款信息
rc_cardNum VARCHAR2(60); --收款单就诊号
rc_patientId NUMBER(12,0); --患者id
rc_patientName VARCHAR2(50); --患者姓名
rc_hosId NUMBER(12,0); --医院id
rc_invoiceCode VARCHAR2(60); --发票号
rc_isSend VARCHAR2(4); --发药状态
rc_status VARCHAR2(4); --收款单状态
--其他变量
v_sendQuantity NUMBER(12,2); --发药数量(基本单位表示的)
v_sendWriteId NUMBER(12,0); --已执行医嘱的id
v_isFirst BOOLEAN; --是否首行发药记录,只有首行写发药主表
v_i NUMBER(12,0); --临时计数用
--待发药医嘱记录游标(此处没有考虑库存批次有效性的验证)
/*列信息:医嘱id,库存id,药品编号,药品名称,药品规格,
发药单位,发药单位与基本单位换算比,发药数量,售价,医嘱发药状态,
用法id,用法名称,药品剂量,用药频次id,用药频次名称,
库存数量(基本单位表示的),药库进价(已换算为发药单位),
库存批号
*/
CURSOR cur_send IS
select a.ORDER_WRITE_ID,c.STOCK_ID,b.DOCTOR_ORDER_CODE,b.DOCTOR_ORDER_NAME,b.SPECIFICATION,
e.PACKAGE_UNIT,e.RELATION,c.QUANTITY,c.PRICE,b.ORDER_STATUS_ID,
b.USAGE_ID,f.USAGE_NAME,b.DOSE,b.FREQUENCY_ID,g.FREQUENCY_NAME,
d.QUANTITY AS STOCK_QUANTITY,ROUND(d.PURCHASE_PRICE/d.RELATION_MAX,4) as PUR_PRICE,
d.BATCH_NUMBER,
(CASE WHEN d.QUANTITY<ROUND(e.RELATION*c.QUANTITY,2) THEN 1 ELSE 0 END) AS LESS_FLAG --库存不足标志
from T_OPD_RECEIVABLES_DETAIL a
inner join T_OPD_DOCTOR_ORDER_WRITE b on a.ORDER_WRITE_ID=b.ID and b.DOCTOR_ORDER_TYPE_ID='drug' and b.EXECUTIVE_SECTION_ID=deptId
inner join T_OPD_QUOTN c on c.ORDER_WRITE_ID=b.ID
inner join T_DRUGSTORE_DRUG_STOCK d on c.STOCK_ID=d.id
inner join T_PHARMACY_DRUG_PACKAGE e on b.DRUG_UNIT_ID=e.id
left join T_SYS_BASIC_USAGE f on b.USAGE_ID=f.ID
left join T_SYS_BASIC_FREQUENCY g on b.FREQUENCY_ID=g.ID
where a.RECEIVABLES_ID=receiveId
ORDER BY b.id,c.id;
BEGIN
mess:='';
--取得收款单主表信息
BEGIN
SELECT CARD_NUM,PATIENT_ID,PATIENT_NAME,HOSPITAL_ID,INVOICE_CODE,IS_SEND_DRUG,STATUS
INTO rc_cardNum,rc_patientId,rc_patientName,rc_hosId,rc_invoiceCode,rc_isSend,rc_status
FROM T_OPD_RECEIVABLES WHERE id=receiveId FOR UPDATE NOWAIT;
EXCEPTION WHEN NO_DATA_FOUND THEN
mess:='待发药收款单'||rc_invoiceCode||'记录不存在!';
RETURN;
END;
--只有收款状态1收款、2退款后生成的新发票,可以进行发药操作
IF NVL(rc_status,'null') NOT IN ('1','2') THEN
mess:='收款单'||rc_invoiceCode||'状态值:'||NVL(rc_status,'null')||',不满足发药操作条件.';
RETURN;
END IF;
--只有发药状态为0未发药、2已打印处方未发药 可以进行发药操作
IF NVL(rc_isSend,'null') NOT IN ('0','2') THEN
mess:='收款单'||rc_invoiceCode||'发药状态值:'||NVL(rc_isSend,'null')||',不满足发药操作条件.';
RETURN;
END IF;
--如果处方未打印过,需要打印处方标志赋值1
needPrint:=(CASE WHEN rc_isSend=2 THEN 0 ELSE 1 END);
--先验证发药记录中是否存在库存不足的批次信息,尽量避免数据修改中途撤销的情况
mess:='';
v_i:=0;
FOR item IN (
select ('品名/规格:'||b.DOCTOR_ORDER_NAME||' '||b.SPECIFICATION||',批号:'||d.BATCH_NUMBER
||',出库量:'||c.QUANTITY||e.PACKAGE_UNIT||',库存量:'||ROUND(d.QUANTITY/e.RELATION,2)) AS stockInfo
from T_OPD_RECEIVABLES_DETAIL a
inner join T_OPD_DOCTOR_ORDER_WRITE b on a.ORDER_WRITE_ID=b.ID and b.DOCTOR_ORDER_TYPE_ID='drug' and b.EXECUTIVE_SECTION_ID=deptId
inner join T_OPD_QUOTN c on c.ORDER_WRITE_ID=b.ID
inner join T_DRUGSTORE_DRUG_STOCK d on c.STOCK_ID=d.id
inner join T_PHARMACY_DRUG_PACKAGE e on b.DRUG_UNIT_ID=e.id
WHERE d.QUANTITY<ROUND(e.RELATION*c.QUANTITY,2) and a.RECEIVABLES_ID=receiveId
ORDER BY b.id,c.id
) LOOP
v_i:=v_i+1;
mess:=mess||item.stockInfo||chr(13);
END LOOP;
IF v_i>0 THEN
mess:='药品库存不足'||chr(13)||mess;
RETURN;
END IF;
--开始业务数据处理,遍历药品医嘱待出库记录进行发药库存处理
v_sendWriteId:=0; --每个医嘱发药时更新医嘱状态为已执行,一条医嘱可能会发多个批次
V_isFirst:=TRUE;
FOR item IN cur_send LOOP
--如果有处理的发药明细,写入发药主表记录
IF V_isFirst THEN
V_isFirst:=FALSE;
sendId:=T_DRUGSTORE_DRUG_SEND_ID.nextval;
INSERT INTO T_DRUGSTORE_DRUG_SEND(ID,CARD_NUM,INVOICE_CODE,PATIENT_ID,PATIENT_NAME,DEPT_ID,HOSPITAL_ID,CREATE_DATE,CREATE_OPER,PRINT_COUNT)
VALUES(sendId,rc_cardNum,rc_invoiceCode,rc_patientId,rc_patientName,deptId,rc_hosId,sysdate,operateId,0);
END IF;
--更新医嘱发药状态
IF (v_sendWriteId!=item.ORDER_WRITE_ID) THEN
v_sendWriteId:=item.ORDER_WRITE_ID;
--如果医嘱状态已执行则返回并提示
IF item.ORDER_STATUS_ID='4' THEN
mess:='已执行过的医嘱 品名/规格:'||item.DOCTOR_ORDER_NAME||' '||item.SPECIFICATION;
GOTO ExitLabel;
END IF;
UPDATE T_OPD_DOCTOR_ORDER_WRITE SET ORDER_STATUS_ID='4',UPDATE_OPER=operateId,UPDATE_DATE=SYSDATE
WHERE ORDER_STATUS_ID!='4' AND ID=v_sendWriteId;
IF SQL%ROWCOUNT<1 THEN
mess:=item.DOCTOR_ORDER_NAME||' 医嘱发药状态更新了0行,医嘱id:'||v_sendWriteId;
GOTO ExitLabel;
END IF;
END IF;
--更新正式库存
v_sendQuantity:=ROUND(item.RELATION*item.QUANTITY,2);
UPDATE T_DRUGSTORE_DRUG_STOCK SET QUANTITY=QUANTITY-v_sendQuantity,UPDATE_OPER=operateId,UPDATE_DATE=SYSDATE
WHERE QUANTITY>=v_sendQuantity AND ID=item.STOCK_ID;
IF SQL%ROWCOUNT<1 THEN
mess:=item.DOCTOR_ORDER_NAME||' 扣减库存更新了0行,库存id:'||item.STOCK_ID;
GOTO ExitLabel;
END IF;
--写入门诊发药明细表
INSERT INTO T_DRUGSTORE_DRUG_SEND_DETAIL(ID,SEND_ID,OPDER_WRITE_ID,DOCTOR_ORDER_ID,DRUG_CODE,
DRUG_NAME,SPECIFICATION,ORDER_DRUG_COUNT,RETURN_COUNT,SEND_COUNT,
PRICE,USAGE_ID,USAGE_NAME,DOSE,FREQUENCY_ID,
FREQUENCY_NAME,PURCHASE_PRICE)
VALUES(T_DRUGSTORE_DRUG_SEND_ID.nextval,sendId,item.ORDER_WRITE_ID,item.STOCK_ID,item.DOCTOR_ORDER_CODE,
item.DOCTOR_ORDER_NAME,item.SPECIFICATION,item.QUANTITY,0,item.QUANTITY,
item.PRICE,item.USAGE_ID,item.USAGE_NAME,item.DOSE,item.FREQUENCY_ID,
item.FREQUENCY_NAME,item.PUR_PRICE);
--对比医嘱药品售格和进价对比,如果低于进价添加到提示信息中
IF item.PUR_PRICE>item.PRICE THEN
priceHint:='门诊发药-库存主键:'||item.STOCK_ID
||'编号:'||item.DOCTOR_ORDER_CODE||'品名:'||item.DOCTOR_ORDER_NAME||chr(13);
END IF;
END LOOP;
--最后判断本收款单所有药品医嘱是否已不存在非4的记录(即均已执行发药),如果是则修改过收款单状态为1已发药。
select COUNT(*) into v_i from DUAL
where exists(
select *
from T_OPD_RECEIVABLES_DETAIL a
inner join T_OPD_DOCTOR_ORDER_WRITE b on a.ORDER_WRITE_ID=b.ID and b.DOCTOR_ORDER_TYPE_ID='drug' and b.ORDER_STATUS_ID!=4
WHERE a.RECEIVABLES_ID=receiveId);
IF v_i=0 THEN
UPDATE T_OPD_RECEIVABLES SET IS_SEND_DRUG=1,UPDATE_OPER=operateId,UPDATE_DATE=SYSDATE WHERE ID=receiveId;
END IF;
mess:='OK';
RETURN;
--游标循环体内退出的情况跳转到这里(循环体内直接RETURN会导致游标未关闭)
<<ExitLabel>>
RETURN;
EXCEPTION
WHEN NO_DATA_FOUND THEN
mess:='查询记录数据为空!';
WHEN OTHERS THEN
mess := SUBSTR(SQLERRM,1,2000);
END P_OPD_SENDDRUG;
/

存储过程调用语句

declare
mess VARCHAR2(800);
sendId INT;
priceHint VARCHAR2(800);
needPrint INT;
BEGIN
P_OPD_SENDDRUG(6923463,3315003,3347533,mess,sendId,priceHint,needPrint);
DBMS_OUTPUT.put_line(mess);
DBMS_OUTPUT.put_line(sendId);
end;
rollback;

JAVA中mybatis中mapper.xml配置

//声明
void opdSendDrug(Map<String,Object> params);
//实现
<select id="opdSendDrug" statementType="CALLABLE" resultType="java.util.Map">
{call P_OPD_SENDDRUG(
#{receiveId,mode=IN,jdbcType=BIGINT},
#{operateId,mode=IN,jdbcType=BIGINT},
#{deptId,mode=IN,jdbcType=BIGINT},
#{mess,mode=OUT,jdbcType=VARCHAR},
#{sendId,mode=OUT,jdbcType=BIGINT},
#{priceHint,mode=OUT,jdbcType=VARCHAR},
#{needPrint,mode=OUT,jdbcType=INT}
)}
</select>
服务层传参调用,这里使用了自有的一个类MapFetcher进行数据转换
    @Override
@Transactional(rollbackFor = Exception.class)
public Long doOpdSendDrug(Long receiveId, Long operateId, Long deptId, Map<String, String> message) {
//调用门诊发药数据库存储过程
Map<String,Object> params=new HashMap<>();
params.put("receiveId", receiveId);
params.put("operateId", operateId);
params.put("deptId",deptId);
mapper.opdSendDrug(params);
//获取存储过程的返回值,返回值非OK的情况代表没有正常结束,抛出异常回滚事务
MapFetcher mapFch = new MapFetcher(params);
String retMess=mapFch.get(String.class,"mess","");
if (!"OK".equals(retMess)){
throw new RuntimeException("门诊发药失败!" +retMess);
}
Long sendDrugId=mapFch.get(Long.class,"sendId");
message.put("priceHint",mapFch.get(String.class,"priceHint"));
message.put("needPrint",mapFch.get(String.class,"needPrint"));
return sendDrugId;
}

ORACLE存储过程编程应用实例-门诊药房发药的更多相关文章

  1. Oracle创建表语句(Create table)语法详解及示例、、 C# 调用Oracle 存储过程返回数据集 实例

    Oracle创建表语句(Create table)语法详解及示例 2010-06-28 13:59:13|  分类: Oracle PL/SQL|字号 订阅 创建表(Create table)语法详解 ...

  2. Oracle存储过程 一个具体实例

    表结构信息,并不是用oracle描述的,但是后面的存储过程是针对oracle的 ----------------个人交易流水表----------------------------------- c ...

  3. Oracle存储过程返回游标实例详解

    复制代码 代码如下:CREATE OR REPLACE PROCEDURE PROCSENDEMAIL(P_TXT VARCHAR2, P_SUB VARCHAR2, P_SENDOR VARCHAR ...

  4. Oracle——存储过程简单入门实例

    1.连接plsql developer,打开一个SQL Window 2.SQL Window中创建表user_info -- Create table create table USER_INFO ...

  5. Oracle存储过程的创建实例和调用实例

    --编写一个存储过程,给emp表中添加数据.  --方法一:create or replace procedure add_employee(       eno number,       name ...

  6. oracle存储过程实例

    oracle存储过程实例 分类: 数据(仓)库及处理 2010-05-03 17:15 1055人阅读 评论(2)收藏 举报 认识存储过程和函数 存储过程和函数也是一种PL/SQL块,是存入数据库的P ...

  7. .NET Core中使用Dapper操作Oracle存储过程最佳实践

    为什么说是最佳实践呢?因为在实际开发中踩坑了,而且发现网上大多数文章给出的解决方法都不能很好地解决问题.尤其是在获取类型为OracleDbType.RefCursor,输出为:ParameterDir ...

  8. NET Core中使用Dapper操作Oracle存储过程

    .NET Core中使用Dapper操作Oracle存储过程最佳实践   为什么说是最佳实践呢?因为在实际开发中踩坑了,而且发现网上大多数文章给出的解决方法都不能很好地解决问题.尤其是在获取类型为Or ...

  9. oracle 存储过程

    来自:http://www.jb51.net/article/31805.htm Oracle存储过程基本语法 存储过程 1 CREATE OR REPLACE PROCEDURE 存储过程名 2 I ...

  10. PL/SQL存储过程编程

    PL/SQL存储过程编程 /**author huangchaobiao *Email:huangchaobiao111@163.com */ PL/SQL存储过程编程(上) 1. Oracle应用编 ...

随机推荐

  1. springboot~jpa优雅的处理isDelete的默认值

    如果多个实体类都有 isDelete 字段,并且你希望在插入时为它们统一设置默认值,可以采取以下几种方法来减少代码重复: 1. 使用基类(抽象类) 创建一个基类,其中包含 isDelete 字段和 @ ...

  2. 牛客小白月赛105 (Python题解)

    牛客小白月赛105 (Python题解) 比赛链接:点击传送 A-lz的吃饭问题 代码: a, b = map(int,input().split()) c, d = map(int,input(). ...

  3. ADMM——交替方向乘子法

    ADMM(Alternating Direction Method of Multipliers,交替方向乘子法)是一种优化算法,主要用于解决分布式.大规模和非光滑的凸优化问题.ADMM通过将原始问题 ...

  4. 玩转AI工作流:一步步搭建灵活的自动化流程

    我们之前搭建了许多不同类型的智能体,其中最受欢迎的就是在智能体中搭建各种工作流--这也是我最喜欢探索和玩的领域.那么,究竟什么是工作流?如何在后端实现一个工作流呢?今天我们就先简单了解下. 什么是工作 ...

  5. SpringMVC-Mybatis-Maven项目整合

    上次不知道为什么,把写好的系列文章都搞成一样了.结果,五篇文章,都变成了最后一篇文章. 悲剧,好吧,只好重新写了. 这系列文章写的是SpringMVC-Mybatis-Maven项目整合.说白了,其实 ...

  6. Chrome插件之油猴(详尽版本)

    官方文档: https://www.tampermonkey.net/documentation.php#google_vignette 1.注释语法: // @match https://passp ...

  7. docker之基本命令(2)

    上一篇介绍了一下,docker使用的有些基础命令.这次再来深入的了解一下较为复杂的docker命令   管理指令 说明 docker  container 用于管理容器 docker image 用于 ...

  8. 如何实现LLM的通用function-calling能力?

    众所周知,LLM的函数function-calling能力很强悍,解决了大模型与实际业务系统的交互问题.其本质就是函数调用. 从openai官网摘图: 简而言之: LLM起到决策的作用,告知业务系统应 ...

  9. 基于云主机的ModelArts模型训练实践,让开发环境化繁为简

    本文分享自华为云社区<[开发者空间实践]云主机安装Docker并制作自定义镜像在ModelArts平台做模型训练>,作者: 开发者空间小蜜蜂. 1.1 案例介绍 在AI业务开发以及运行的过 ...

  10. 技术实践|数据迁移中GBK转UTF8字符集问题分析

    导语:在国产化创新的大背景下,数据库迁移项目逐渐增多,在数据库迁移过程中,源数据库和目标数据库字符集有时会不同,这时如何进行字符集转换则成为了一个重要的问题,同时在转换过程中还需要确保数据的完整性和一 ...