Oracle根据总和从查询集中删除行
Oracle remove rows from a query set based on sum
我有一个 table 数据如下。 INVENTORY_ITEM_ID 是商品的唯一 ID,TYPE_QTY 是商品在唯一年龄段 (AGE_IN_DAYS) 中的总数量,RUNNING_TOTAL 是基于TYPE_QTY 在年龄段上。
需要从最后一组行中删除每个负数量。例如,在第一次出现-508 的负数量时,应识别并调整第一行总计 运行 可以满足 508 发行的行。该行上方的所有行都应从结果集中删除。
RUNNING_TOTAL 和 TYPE_QTY 列用 (555-508) 的余额进行调整,循环继续。第二次发布 -22 数量发生在第一行,因为它有 运行 总数 47,给定数据的最终结果应该如下所示
我制作了一个 PL/SQL 块可以完成这项工作,但我更愿意使用普通 SQL 来实现它。我目前的SQL技能还不够。
PL/SQL 块
SET SERVEROUTPUT ON;
DECLARE
CURSOR INVDATA IS
SELECT tx.*
from OMSINVDT_TEMP tx
--where inventory_item_id = 35253
order by inventory_item_id,age_in_days desc;
CURSOR inline_data(p_item_id IN NUMBER) IS
SELECT inventory_item_id,
type_qty,
age_in_days,
SUM (type_qty) OVER ( PARTITION BY inventory_item_id ORDER BY age_in_days desc) RUNNING_TOTAL
FROM omsinvdata_temp
where inventory_item_id = p_item_id;
l_line_qty number;
l_last_age_period number := 0;
BEGIN
execute immediate 'truncate table omsinvdata_temp';
for i in invdata loop
--if the qty is greater than 0, add to the temp table
if i.type_qty > 0 then
insert into omsinvdata_temp(
inventory_item_id ,
type_qty ,
age_in_days ,
running_total )
values(i.inventory_item_id, i.type_qty, i.age_in_days, 0);
else
--if the quantity is negative
--open the cursor for the item from temporary table
--and find the row that can satisfy the negative quantity
--dbms_output.put_line('current quantity: '||i.type_qty);
for j in inline_data(i.inventory_item_id) loop
--dbms_output.put_line('Line Qty '||j.type_qty||' Running total: '||j.running_total||' To Issue: '||i.type_qty||' Bucket '||j.age_in_days);
if (abs(i.type_qty)>j.running_total) then
-- dbms_output.put_line('Running total: '||j.running_total||' not sufficient to issue '||i.type_qty||' Bucket '||j.age_in_days);
update omsinvdata_temp
set type_qty =0,
running_total =0
where age_in_days = j.age_in_days
and inventory_item_id = i.inventory_item_id;
else
-- dbms_output.put_line('Running total: '||j.running_total||' sufficient to issue '||i.type_qty||' Bucket '||j.age_in_days);
update omsinvdata_temp
set type_qty = j.running_total + i.type_qty,
running_total = j.running_total + i.type_qty
where age_in_days = j.age_in_days
and inventory_item_id = i.inventory_item_id;
exit;
end if;
end loop;
end if;
end loop;
commit;
END;
使用显示的数据创建 table 脚本
CREATE TABLE "OMSINVDT_TEMP" ("INVENTORY_ITEM_ID" NUMBER, "TYPE_QTY" NUMBER, "AGE_IN_DAYS" NUMBER, "RUNNING_TOTAL" NUMBER)
REM INSERTING into OMSINVDT_TEMP
SET DEFINE OFF;
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,72,6,72);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,384,5,456);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,105,4,561);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,-512,3,49);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,-24,2,25);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,134,1,159);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,2,4,2);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,1,3,3);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,-1,2,2);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,-2,1,0);
commit;
我已经向 Oracle SQL/PLSQL 社区询问过这个问题,但未能解释构造逻辑。
https://community.oracle.com/tech/developers/discussion/4480421/sql-match-quantity-and-pick-rows
这是一种方法,仅使用分析函数和聚合。您没有解释输出中的 AGE_IN_DAYS
列 - 根据您的示例,我假设它代表 positive 行中最近的年龄 在 最后一个负数行之前。
RUNNING_TOTAL
列不应存在于输入中,因为它是根据其他数据计算得出的。即使你在 table 中有它,我也会忽略它 - 我直接计算它。 (我假设你展示的不是你真正的起始数据,而是你无法继续你的解决方案的地方。)
您使用的示例与您的 INSERT
语句也不匹配。我按原样使用了 INSERT
语句(其中一行具有不同的值);这解释了为什么我的输出看起来与您的不同。
主要技巧在 WITH
子句中,在 PREP
子查询中。我为 行 最后一个“负”行分配了一个标志。然后在主查询中,我按这个标志分组,除此之外,仅当标志设置 时,按 AGE_IN_DAYS。这样,直到并包括最后一个“负”行的所有行都在一组中,而剩余的正行是每组一行。 (我假设 AGE_IN_DAYS
对于每个 INVENTORY_ITEM_ID
都是不同的;如果不是,我可以使用其他东西,例如 ROWNUM
- 但问题无论如何都不会被很好地定义.)
所以,就在这里。如果您有任何问题,请查看并告诉我。
with prep as (
select inventory_item_id, type_qty, age_in_days,
case count(case when type_qty < 0 then 1 end)
over (partition by inventory_item_id order by age_in_days)
when 0 then 'Y' end as past_last_negative
from omsinvdt_temp
)
select inventory_item_id, sum(type_qty) as type_qty,
min(case when type_qty > 0 then age_in_days end) as age_in_days,
sum(sum(type_qty)) over (partition by inventory_item_id
order by max(age_in_days)) as running_total
from prep
group by inventory_item_id,
case past_last_negative when 'Y' then age_in_days end
order by inventory_item_id, age_in_days desc
;
INVENTORY_ITEM_ID TYPE_QTY AGE_IN_DAYS RUNNING_TOTAL
----------------- ---------- ----------- -------------
35253 25 4 159
35253 134 1 134
266234 0 3 0
我有一个 table 数据如下。 INVENTORY_ITEM_ID 是商品的唯一 ID,TYPE_QTY 是商品在唯一年龄段 (AGE_IN_DAYS) 中的总数量,RUNNING_TOTAL 是基于TYPE_QTY 在年龄段上。
需要从最后一组行中删除每个负数量。例如,在第一次出现-508 的负数量时,应识别并调整第一行总计 运行 可以满足 508 发行的行。该行上方的所有行都应从结果集中删除。
RUNNING_TOTAL 和 TYPE_QTY 列用 (555-508) 的余额进行调整,循环继续。第二次发布 -22 数量发生在第一行,因为它有 运行 总数 47,给定数据的最终结果应该如下所示
我制作了一个 PL/SQL 块可以完成这项工作,但我更愿意使用普通 SQL 来实现它。我目前的SQL技能还不够。
PL/SQL 块
SET SERVEROUTPUT ON;
DECLARE
CURSOR INVDATA IS
SELECT tx.*
from OMSINVDT_TEMP tx
--where inventory_item_id = 35253
order by inventory_item_id,age_in_days desc;
CURSOR inline_data(p_item_id IN NUMBER) IS
SELECT inventory_item_id,
type_qty,
age_in_days,
SUM (type_qty) OVER ( PARTITION BY inventory_item_id ORDER BY age_in_days desc) RUNNING_TOTAL
FROM omsinvdata_temp
where inventory_item_id = p_item_id;
l_line_qty number;
l_last_age_period number := 0;
BEGIN
execute immediate 'truncate table omsinvdata_temp';
for i in invdata loop
--if the qty is greater than 0, add to the temp table
if i.type_qty > 0 then
insert into omsinvdata_temp(
inventory_item_id ,
type_qty ,
age_in_days ,
running_total )
values(i.inventory_item_id, i.type_qty, i.age_in_days, 0);
else
--if the quantity is negative
--open the cursor for the item from temporary table
--and find the row that can satisfy the negative quantity
--dbms_output.put_line('current quantity: '||i.type_qty);
for j in inline_data(i.inventory_item_id) loop
--dbms_output.put_line('Line Qty '||j.type_qty||' Running total: '||j.running_total||' To Issue: '||i.type_qty||' Bucket '||j.age_in_days);
if (abs(i.type_qty)>j.running_total) then
-- dbms_output.put_line('Running total: '||j.running_total||' not sufficient to issue '||i.type_qty||' Bucket '||j.age_in_days);
update omsinvdata_temp
set type_qty =0,
running_total =0
where age_in_days = j.age_in_days
and inventory_item_id = i.inventory_item_id;
else
-- dbms_output.put_line('Running total: '||j.running_total||' sufficient to issue '||i.type_qty||' Bucket '||j.age_in_days);
update omsinvdata_temp
set type_qty = j.running_total + i.type_qty,
running_total = j.running_total + i.type_qty
where age_in_days = j.age_in_days
and inventory_item_id = i.inventory_item_id;
exit;
end if;
end loop;
end if;
end loop;
commit;
END;
使用显示的数据创建 table 脚本
CREATE TABLE "OMSINVDT_TEMP" ("INVENTORY_ITEM_ID" NUMBER, "TYPE_QTY" NUMBER, "AGE_IN_DAYS" NUMBER, "RUNNING_TOTAL" NUMBER)
REM INSERTING into OMSINVDT_TEMP
SET DEFINE OFF;
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,72,6,72);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,384,5,456);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,105,4,561);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,-512,3,49);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,-24,2,25);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (35253,134,1,159);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,2,4,2);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,1,3,3);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,-1,2,2);
Insert into OMSINVDT_TEMP (INVENTORY_ITEM_ID,TYPE_QTY,AGE_IN_DAYS,RUNNING_TOTAL) values (266234,-2,1,0);
commit;
我已经向 Oracle SQL/PLSQL 社区询问过这个问题,但未能解释构造逻辑。
https://community.oracle.com/tech/developers/discussion/4480421/sql-match-quantity-and-pick-rows
这是一种方法,仅使用分析函数和聚合。您没有解释输出中的 AGE_IN_DAYS
列 - 根据您的示例,我假设它代表 positive 行中最近的年龄 在 最后一个负数行之前。
RUNNING_TOTAL
列不应存在于输入中,因为它是根据其他数据计算得出的。即使你在 table 中有它,我也会忽略它 - 我直接计算它。 (我假设你展示的不是你真正的起始数据,而是你无法继续你的解决方案的地方。)
您使用的示例与您的 INSERT
语句也不匹配。我按原样使用了 INSERT
语句(其中一行具有不同的值);这解释了为什么我的输出看起来与您的不同。
主要技巧在 WITH
子句中,在 PREP
子查询中。我为 行 最后一个“负”行分配了一个标志。然后在主查询中,我按这个标志分组,除此之外,仅当标志设置 时,按 AGE_IN_DAYS。这样,直到并包括最后一个“负”行的所有行都在一组中,而剩余的正行是每组一行。 (我假设 AGE_IN_DAYS
对于每个 INVENTORY_ITEM_ID
都是不同的;如果不是,我可以使用其他东西,例如 ROWNUM
- 但问题无论如何都不会被很好地定义.)
所以,就在这里。如果您有任何问题,请查看并告诉我。
with prep as (
select inventory_item_id, type_qty, age_in_days,
case count(case when type_qty < 0 then 1 end)
over (partition by inventory_item_id order by age_in_days)
when 0 then 'Y' end as past_last_negative
from omsinvdt_temp
)
select inventory_item_id, sum(type_qty) as type_qty,
min(case when type_qty > 0 then age_in_days end) as age_in_days,
sum(sum(type_qty)) over (partition by inventory_item_id
order by max(age_in_days)) as running_total
from prep
group by inventory_item_id,
case past_last_negative when 'Y' then age_in_days end
order by inventory_item_id, age_in_days desc
;
INVENTORY_ITEM_ID TYPE_QTY AGE_IN_DAYS RUNNING_TOTAL
----------------- ---------- ----------- -------------
35253 25 4 159
35253 134 1 134
266234 0 3 0