上周先生给我出了道题,感觉非常具有挑战性,大半年没玩高难度SQL感觉水平下降不少,两种解法花了12个小时完成,脑细胞牺牲不少。
题目如下所示:

题目需要花几分钟时间仔细观察,说明:
将DEL和PM表合并插入到pay表,依据PM表的付款金额和DEL的送货金额进行数据拆分重组,
从首条开始,当付款金额>=送货金额时 第PAY表第一条数据的 送货日期 送货金额 付款日期 均为 DEL和PM表的第一行记录,只有付款金额变成送货金额 ,同时生成第二条记录
第二条记录的送货日期取DEL表的第二行记录 送货金额+付款金额=第一行的付款金额-送货金额的差值 付款日期取PM表的第一行记录
第三条记录 送货日期 +付款日期取 DEL+PM表第二行记录,送货金额+付款金额=
第四条记录 送货日期 +付款日期取 DEL+PM表第三行记录,送货金额+付款金额=第二行付款金额-(送货金额第二行金额 -(第一行的付款金额-送货金额的差值))
第五条记录 送货日期 +付款日期取 DEL+PM表第三行记录,送货金额+付款金额=第三条付款金额
以上只是一种正常的数据理解说明,如果这里的付款金额与送货金额发生大小变动还需要调整,具体需要联想到业务改金额测试校验下。
---------------------先发简单的PL动态SQL解法-------------------------------
前置表建好:
create or replace procedure SH_TEST is
type cur_mony is ref cursor;
cur_monye cur_mony;
v_rn number;
v_ht del.合同号%TYPE;
v_elivery_date del.送货日期%TYPE;
v_elivery_date2 del.送货日期%TYPE;
v_elivery_mony del.送货金额%TYPE;
v_elivery_mony2 del.送货金额%TYPE;
v_rnmax number;
v_payment_date pm.付款日期%TYPE;
v_payment_date2 pm.付款日期%TYPE;
v_payment_mony pm.付款金额%TYPE;
v_amountd pm.付款金额%TYPE;
v_amountd2 pm.付款金额%TYPE;
v_nm number ;
BEGIN
--计数器
v_nm :=0;
open cur_monye for select del.rn from (select a.*,rownum rn from del a)del ,( select a.*,rownum rn from pm a)pm
where del.合同号=pm.合同号 and del.rn=pm.rn ;
loop
fetch cur_monye into v_rn;
BEGIN
SELECT A.合同号, A.送货日期,A.送货金额 ,B.付款日期, B.付款金额,A.送货日期2,A.送货金额2+A.送货金额-B.付款金额-b.付款金额2,
nvl(送货金额2,0), (A.送货金额3- B.付款金额3),付款日期4,
max(a.rn) over (partition by a.rn)
INTO v_ht,v_elivery_date, v_elivery_mony ,v_payment_date, v_payment_mony,v_elivery_date2,v_amountd,
v_elivery_mony2,v_amountd2,v_payment_date2,v_rnmax
FROM (select a.*,lead(a.送货日期) over (order by a.送货日期) as 送货日期2,
lag(a.送货日期) over (order by a.送货日期) as 送货日期3,
lag(a.送货金额) over (order by a.送货金额) as 送货金额2,
lead(a.送货金额) over (order by a.送货金额) as 送货金额3,
rownum RN from del a where 合同号='A' ) A,
(select b.*,
lag(b.付款金额) over (order by b.付款金额) as 付款金额2,
lag(B.付款日期) over (order by B.付款日期) as 付款日期4,
lead(b.付款金额) over (order by b.付款金额) as 付款金额3,
rownum RN from pm b where 合同号='A' ) B
WHERE A.合同号=B.合同号 AND A.RN = B.RN and a.rn=v_rn ;
--解决游标尾数额外循环一次BUG
v_nm := v_nm+1;
end;
----计算付款大于送货金额头部数据
IF v_payment_mony >= v_elivery_mony and v_nm<v_rnmax+1 THEN
insert into pay select v_ht, v_elivery_date,v_elivery_mony,v_payment_date,v_elivery_mony,1 from dual;
insert into pay select v_ht, v_elivery_date2,v_payment_mony-v_elivery_mony,v_payment_date,v_payment_mony-v_elivery_mony,1 from dual;
commit;
end if ;
----计算 计算付款小于送货金额且 尾部与中间差额数据
IF v_payment_mony < v_elivery_mony and v_nm<v_rnmax+1 and v_amountd is null
THEN
insert into pay select v_ht, v_elivery_date, v_payment_mony-v_amountd2 , v_payment_date,
v_payment_mony-v_amountd2 ,2 from dual;
commit;
END IF;
-----计算付款小于送货金额且(本次+上行送货金额)-(本次+上行付款金额)>0 尾部数据
IF v_payment_mony < v_elivery_mony and v_nm<v_rnmax+1 and v_amountd IS NOT NULL
THEN
insert into pay select v_ht, v_elivery_date, v_elivery_mony-v_payment_mony ,v_payment_date2, v_elivery_mony-v_payment_mony ,3
from dual;
insert into pay select v_ht, v_elivery_date, v_payment_mony ,v_payment_date, v_payment_mony ,3
from dual;
dbms_output.put_line(v_rn);
commit;
end if;
exit when cur_monye%notfound;
dbms_output.put_line('结束');
end loop;
close cur_monye;
END SH_TEST;
最终效果如下图:

with del as (
select 'A' as 合同号,'1.1' as 送货日期,100 as 送货金额 from dual
union
select 'A' as 合同号,'1.10' as 送货日期,200 as 送货金额 from dual
union
select 'A' as 合同号,'1.20' as 送货日期,300 as 送货金额 from dual
)
,pm as (
select 'A' as 合同号,'2.10' as 付款日期,250 as 付款金额 from dual
union
select 'A' as 合同号,'2.20' as 付款日期,100 as 付款金额 from dual
union
select 'A' as 合同号,'2.25' as 付款日期,250 as 付款金额 from dual
)
,v1 as ( select del.合同号,del.送货日期,
del.送货金额 ,pm.付款日期, pm.付款金额,
pm.付款金额-del.送货金额 付款金额差 ,rownum rn from
(select a.*,rownum rn from del a)del ,
(select a.*,rownum rn from pm a)pm
where del.合同号=pm.合同号 and del.rn=pm.rn )
,tmony (合同号,送货日期,送货金额, 付款日期, 付款金额,付款金额差,送货日期2,付款日期2,付款金额2,送货金额2,付款金额差2,送货金额ZS,ITERATER) AS
( select 合同号,送货日期,送货金额, 付款日期, 付款金额,付款金额差,'1','1',1,1,1,1,1 from v1
UNION ALL
SELECT
t1.合同号,
t1.送货日期,
t1.送货金额,
t1.付款日期,
t1.付款金额,
t1.付款金额差 ,
t2.送货日期 送货日期2,
t2.付款日期 付款日期2,
t2.付款金额 付款金额2,
t2.送货金额 送货金额2,
t2.付款金额差 付款金额差2,
t1.付款金额-t2.送货金额 送货金额ZS,
ITERATER + 1
FROM v1 t1, tmony t2
WHERE t1.rn = t2.ITERATER
)
---冗余金额首
select 合同号,送货日期,送货金额, 付款日期, 送货金额 as 付款金额 ,1
from tmony where ITERATER=2 and 付款金额差2>0 and 送货金额ZS>0
union all
----增补金额
select 合同号,
case when 付款金额差2>0 then lead(送货日期2) over (order by 送货日期2) else 送货日期2 end 送货日期,
case when 付款金额差2>0 and 送货金额ZS >0 then 付款金额差
when 付款金额差2>0 and 送货金额ZS <0 then 送货金额2
when 付款金额差2<0 and 送货金额ZS>0 then 送货金额ZS
when 付款金额差2<0 and 送货金额ZS<0 then 送货金额2-付款金额2 else 送货金额 end 送货金额 ,
case when 付款金额差2>0 then 付款日期
when 付款金额差2<0 and 送货金额ZS<0 then lag(付款日期2) over (order by 付款日期2) else 付款日期2 end 付款日期,
case when 付款金额差2>0 and 送货金额ZS >0 then 付款金额差
when 付款金额差2>0 and 送货金额ZS <0 then 送货金额2
when 付款金额差2<0 and 送货金额ZS>0 then 送货金额ZS
when 付款金额差2<0 and 送货金额ZS<0 then 送货金额2-付款金额2 else 送货金额 end 付款金额
,2 from tmony where ITERATER=2
---冗余金额尾
union all
select 合同号,送货日期2 as 送货日期,付款金额2 as 送货金额, 付款日期2 as 付款日期,付款金额2 as 付款金额 ,3
from tmony where ITERATER=2 and 送货金额ZS<0 and 付款金额差2<0
以下是结果:

这个没有过多的说明,无需建表,一条SQL搞定,主要是调试太多次了,长达8小时的测试成果,其实这里还有很大的美观精简优化空间,感兴趣可以自行优化。。




