Oracle根据总和从查询集中删除行

Posted

技术标签:

【中文标题】Oracle根据总和从查询集中删除行【英文标题】:Oracle remove rows from a query set based on sum 【发布时间】:2021-02-12 06:16:46 【问题描述】:

我有一个表格,其中包含如下数据。 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;

使用显示的数据创建表格脚本

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

【问题讨论】:

这里的逻辑我看不懂。 你能解释一下具体的逻辑部分吗? 我不太明白的部分(不代表 Tim B):假设输入依次为 30、50、80、-20(对应的运行总数:30、80 , 160, 140)。你是说现在你必须替换所有四行,而是只显示一个输入 140,运行总数为 140?如果是这样,那么问题实际上简化为只找到序列中的最后一个负数,并将它和所有先前的行替换为单行。但是,我认为这不是所需的处理;但如果不是,则说明您没有正确解释。 另外 - 您是否要从表中删除行并插入新行?或者只是编写一个产生所需输出的查询? (或者,等效地,编写一个 view 可以在未来的任何时间计算相同的输出?) @mathguy,考虑案例 30,50,80,-20, 40,60。针对您的示例,我需要选择具有负值的最后一行(总共 140 行)和序列中的其余行。这将是运行总数为 140、180,240 的行。 【参考方案1】:

这是一种方法,仅使用分析函数和聚合。您没有解释输出中的 AGE_IN_DAYS 列 - 根据您的示例,我假设它表示最近的 positivepreceding 最后一行的年龄负行。

RUNNING_TOTAL 列不应存在于输入中,因为它是根据其他数据计算得出的。即使你有它在表中,我忽略它 - 我直接计算它。 (我假设您显示的不是您真正的起始数据,而是您无法继续解决方案的点。)

您使用的示例与您的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

【讨论】:

结果集正是我所期待的。请允许我将其应用于更大的数据集并回复您。谢谢! 我已经对查询进行了微小的更改(将 max(age_in_days) 更改为 DESC),您提供的查询正在生成由我的 PL/SQL 块生成的确切数据。我接受了您的解决方案作为答案。谢谢 我想解决我今天在工作中发现的一个小问题。如果在具有正值的行之后还有两行具有负值。你愿意帮忙吗? @RajeshThampi - 当然。有什么顾虑?你认为查询不起作用吗? (我认为确实如此 - 您的样本数据已经包含在正行之后的两个负行的情况)。还是您的问题陈述不正确?请注意,在我编写解决方案之前,我要求您确认该声明。 对于延迟回复,我深表歉意。实际上,我们在使用 *** 时遇到了麻烦,我们的周末是在周五和周六。我将使用您接受的解决方案关闭此线程并打开一个新线程,因为我相信要求已经改变。谢谢?

以上是关于Oracle根据总和从查询集中删除行的主要内容,如果未能解决你的问题,请参考以下文章

R:从R中的大型数据集中根据列中的值删除行[重复]

大查询 |根据运行总和为行分配数字

Oracle:根据结果集计数更新行

如何根据分组变量计算所有列的总和并删除 NA

Django:如何根据来自行的数据和来自另一个模型的数据将聚合字段添加到查询集中?

如何根据 Impala 查询中的调整找到值的总和