Oracle:从收费明细中扣减退费数据

1、基本假设

假设:收费明细中含有退费数据和被退费数据,其中包含全退和部分退。

全退:原本一条收费记录,收费项目是铅笔,数量为3,现在把3只铅笔全部退掉,我们不会删除原始的收费数据,而是增加一条数量为-3的记录

部分退:原本一条收费记录,收费项目是铅笔,数量为3,现在要退掉1只铅笔,我们不会删除原始的收费数据,而是增加一条数量为-1的记录

如果是自己的数据库,我们肯定会用一个字段记录退费记录、被退费记录、正常收费没有被退费的记录

2、使用场景

在某些场景下,我们需要对别人导出的收费明细进行表关联,但是因为某种原因,导出的收费明细中没有区分退费记录、被退费记录、正常收费记录的字段,我们希望过滤掉退费数据,筛选出满足某个条件的、没有被退费的记录

现在提供一种基于CTE和窗口函数的实现办法:

3、最终目标

(1)过滤掉所有的退费记录

(2)过滤掉所有被全退的记录

(3)修正部分退的记录,例如三支铅笔,退一支铅笔,有效数量是2

4、算法核心思想

(1)根据 结算单据号、结算日期、医院项目名称 对原始收费明细进行分组,并且根据rowid和数量在分组内确定每条记录的序号,将数量为负数的记录排列在前面

(2)在每个分组内,从上到下,从第一条记录到当前记录的累计金额

(3)在每个分组内,从上到下,确定每条记录的待扣减数量,待扣减数量,告诉后续记录还需要扣减多少

(4)根据前一条记录的待扣减数量和当前记录的数量,计算当前记录的修正数量

(5)过滤所有负费用记录、被全退费的正费用记录

5、SQL算法实现

sql 复制代码
WITH 
-- CHARGE_DETAIL为原始结算明细数据,至少包含以下字段:
-- rowid
-- 结算单据号
-- 结算日期
-- 医院项目名称 
-- 数量
-- 金额
 
-- 在原始收费明细(含退费)的基础上,增加rn字段
CHARGE_DETAIL_1 AS (
    SELECT 
        TO_NUMBER(B.数量) AS 原始数量,
        TO_NUMBER(B.金额) AS 原始金额,
        ROW_NUMBER() OVER (
            PARTITION BY B.结算单据号, B.结算日期, B.医院项目名称
            ORDER BY TO_NUMBER(B.数量),B.rowid
        ) AS rn,
        B.结算单据号, B.结算日期, B.医院项目名称
    FROM CHARGE_DETAIL B
),
-- 计算同一个结算单据号上相同项目的累计总额
CHARGE_DETAIL_2 AS (
    SELECT
    SUM(原始数量) 
    OVER (
        PARTITION BY 结算单据号,结算日期,医院项目名称 
        ORDER BY rn ROWS UNBOUNDED PRECEDING
    ) AS 累计总额,
    t.*
    FROM CHARGE_DETAIL_1 t
),
-- 计算每个明细需要扣减的数量
CHARGE_DETAIL_3 AS (
    SELECT
    case 
        when t.rn = 1 and t.原始数量 > 0 then 0 
        when t.rn = 1 and t.原始数量 = 0 then 0
        when t.rn = 1 and t.原始数量 < 0 then  t.原始数量
        when t.rn > 1 and LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) > 0 then 0 
        when t.rn > 1 and LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) = 0 then 
            case
                when t.原始数量 < 0 then t.原始数量
                else 0
            end
        when t.rn > 1 and LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) < 0 then 
            case
                when (LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) + t.原始数量) < 0 then 
                    (LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) + t.原始数量)
                when (LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) + t.原始数量) = 0 then 0
                when (LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) + t.原始数量) > 0 then 0
            end
        else 0
    end as 还需扣减,
    t.*
    FROM CHARGE_DETAIL_2 t
),
-- 计算最终的修正数量
CHARGE_DETAIL_4 AS (
    SELECT
    case 
        when LAG(t.还需扣减,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) < 0 and t.原始数量> 0 then 
            GREATEST((LAG(t.累计总额,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) + t.原始数量),0)
        when t.原始数量> 0 then t.原始数量
        else 0
    end as 修正后的数量,
    case 
        when LAG(t.还需扣减,1,0)  OVER ( PARTITION BY t.结算单据号,t.结算日期,t.医院项目名称 ORDER BY t.rn) < 0 then 1
        when t.原始数量< 0 then 1
        else 0
    end as 数量是否被修正,
    t.*
    FROM CHARGE_DETAIL_3 t
),
-- 过滤原始数量为负数的数据
-- 过滤修正后数量为0的数据
CHARGE_DETAIL_WITHOUT_REFUND AS (
    SELECT
      t.原始数量,
      t.原始金额,
      t.修正后的数量 as 数量,
      t.结算单据号, t.结算日期, t.医院项目名称
    FROM CHARGE_DETAIL_4 t
    WHERE t.修正后的数量 > 0
)
 
SELECT * FROM CHARGE_DETAIL_WITHOUT_REFUND
相关推荐
zhangjw345 小时前
第15篇:Java多线程零基础入门,进程线程、线程创建方式、线程生命周期、线程安全彻底吃透
java·开发语言·面试
蝈理塘(/_\)大怨种5 小时前
类和对象 (上)
java·开发语言
程序猿阿伟5 小时前
《一套完整方法论:搞定图形应用的Docker镜像优化》
数据库·docker·容器
lihui_cbdd5 小时前
HPC 集群上 OpenMM GPU 多版本安装实战指南
运维·服务器·人工智能·计算化学
二等饼干~za8986686 小时前
geo优化源码开发搭建技术分享
大数据·网络·数据库·人工智能·音视频
Xpower 176 小时前
MCP 服务器暴露在公网:AI Agent 工具层正在变成新的安全边界
服务器·人工智能·安全
我材不敲代码6 小时前
Python 函数核心:位置参数与关键字参数详解
java·前端·python
数据库小学妹6 小时前
HTAP混合负载架构:如何用一个数据库同时搞定交易和分析
数据库·经验分享·架构·dba
wuxinyan1236 小时前
工业级大模型学习之路029:解决双智能体调用数据库报错问题
数据库·人工智能·python·学习·智能体
Elastic 中国社区官方博客6 小时前
Elastic 线下 Meetup 将于 2026 年 7 月 26 号下午在深圳举行
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索