【问题标题】:LISTAGG equivalent with windowing clauseLISTAGG 等价于加窗子句
【发布时间】:2012-06-08 16:50:55
【问题描述】:

在 oracle 中,LISTAGG 函数允许我通过 OVER (PARTITION BY column..) 子句进行分析使用。但是,它不支持使用带有 ROWSRANGE 关键字的窗口。

我有一个来自存储寄存器的数据集(针对问题进行了简化)。请注意,注册表的数量始终为 1 - 一项,一项交易行。

TranID TranLine ItemId OrderID Dollars Quantity
------ -------- ------ ------- ------- --------
1      101      23845  23      2.99    1
1      102      23845  23      2.99    1
1      103      23845  23      2.99    1
1      104      23845  23      2.99    1
1      105      23845  23      2.99    1

我必须将此数据“匹配”到特殊订单系统中的表格,其中项目按数量分组。请注意,系统可以在多行上具有相同的商品 ID(即使商品相同,订购的组件也可能不同)。

ItemId OrderID Order Line Dollars Quantity
------ ------- ---------- ------- --------
23845  23      1          8.97    3
23845  23      2          5.98    2

唯一我可以匹配这些数据的方法是通过订单 ID、项目 ID 和美元金额。

基本上我需要得到以下结果。

ItemId OrderID Order Line Dollars Quantity Tran ID  Tran Lines
------ ------- ---------- ------- -------- -------  ----------
23845  23      1          8.97    3        1        101;102;103
23845  23      2          5.98    2        1        104;105

我并不特别关心 tran 行是否以任何方式排序,我只关心美元金额是否匹配,并且我不会“重复使用”寄存器中的一行来计算总数特殊订单。我不需要将 tran 行拆分为表格 - 这是出于报告目的,并且粒度永远不会回到注册事务行级别。

我最初的想法是,我可以使用分析函数来执行此操作,以进行“最佳匹配”,以识别与订购系统中的美元金额和数量匹配的第一组行,给我一个结果集,如下所示:

TranID TranLine ItemId OrderID Dollars Quantity CumDollar  CumQty
------ -------- ------ ------- ------- -------- --------   ------
1      101      23845  23      2.99    1        2.99       1
1      102      23845  23      2.99    1        5.98       2
1      103      23845  23      2.99    1        8.97       3
1      104      23845  23      2.99    1        11.96      4
1      105      23845  23      2.99    1        14.95      5

到目前为止一切顺利。但我随后尝试将 LISTAGG 添加到我的查询中:

SELECT tranid, tranline, itemid, orderid, dollars, quantity, 
       SUM(dollars) OVER (partition by tranid, itemid, orderid order by tranline) cumdollar,
       SUM(quantity) OVER (partition by tranid, itemid, orderid order by tranline) cumqty
       LISTAGG (tranline) within group (order by tranid, itemid, orderid, tranline) OVER (partition by tranid, itemid, orderid)
FROM table

我发现它总是返回一个完整的 agg 而不是累积的 agg:

TranID TranLine ItemId OrderID Dollars Quantity CumDollar  CumQty ListAgg
------ -------- ------ ------- ------- -------- --------   ------ -------
1      101      23845  23      2.99    1        2.99       1      101;102;103;104;105
1      102      23845  23      2.99    1        5.98       2      101;102;103;104;105
1      103      23845  23      2.99    1        8.97       3      101;102;103;104;105
1      104      23845  23      2.99    1        11.96      4      101;102;103;104;105
1      105      23845  23      2.99    1        14.95      5      101;102;103;104;105

所以这没有用。

如果可能的话,我更愿意在 SQL 中执行此操作。我知道我可以用游标和程序逻辑来做到这一点。

有没有办法使用 LISTAGG 分析函数或其他支持此功能的分析函数进行窗口化?

我正在使用 11gR2。

【问题讨论】:

  • +1 表示写得非常有趣的问题。

标签: sql oracle oracle11g


【解决方案1】:

我能想到的唯一方法是使用相关子查询:

WITH CTE AS
(   SELECT  TranID, 
            TranLine, 
            ItemID, 
            OrderID, 
            Dollars, 
            Quantity, 
            SUM(dollars) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumDollar, 
            SUM(Quantity) OVER (PARTITION BY TranID, ItemID, OrderID ORDER BY TranLine) AS CumQuantity
    FROM    T
)
SELECT  TranID, 
        TranLine, 
        ItemID, 
        OrderID, 
        Dollars, 
        Quantity, 
        CumDollar, 
        CumQuantity, 
        (   SELECT  LISTAGG(Tranline, ';') WITHIN GROUP(ORDER BY CumQuantity)
            FROM    CTE T2
            WHERE   T1.CumQuantity >= T2.CumQuantity
            AND     T1.ItemID = T2.ItemID
            AND     T1.OrderID = T2.OrderID
            AND     T1.TranID = T2.TranID
            GROUP BY tranid, itemid, orderid
        ) AS ListAgg
FROM    CTE T1;

我意识到这并没有给出您所要求的确切输出,但希望它足以克服累积 LISTAGG 的问题并让您继续前进。

我已经设置了一个SQL Fiddle 来演示解决方案。

【讨论】:

  • 谢谢。它适用于我提供的初始示例,但是当我添加其他事务/项目时,我收到 ORA-01427: single-row subquery returns more than one row 错误。我想我也许可以修改您的查询来做我正在寻找的东西。这是一个引发错误的示例:sqlfiddle.com/#!4/53b4d/1
  • 还有 - SQLFiddle?极好的!肯定会进入书签。
  • 我需要做的就是将and t1.itemid = t2.itemid and t1.orderid = t2.orderid and t1.tranid = t2.tranid 添加到子查询的连接中。你能更新答案吗,我会接受的。
【解决方案2】:

在您的示例中,您的商店登记表包含 5 行,您的特殊订单系统表包含 2 行。您的预期结果集包含您的特殊订单系统表中的两行,并且应在“Tran Line”列中提及您的商店寄存器表的所有“tranlines”。

这意味着您需要将这 5 行聚合为 2 行。这意味着您不需要 LISTAGG 分析函数,而是需要 LISTAGG 聚合函数。

您的挑战是将商店寄存器表的行连接到特殊订单系统表中的右侧行。通过计算美元和数量的运行总和,您的进展顺利。唯一缺少的步骤是定义美元和数量的范围,您可以通过这些范围将每个商店登记行分配给每个特殊订单系统行。

这是一个例子。首先定义表:

SQL> create table store_register_table (tranid,tranline,itemid,orderid,dollars,quantity)
  2  as
  3  select 1, 101, 23845, 23, 2.99, 1 from dual union all
  4  select 1, 102, 23845, 23, 2.99, 1 from dual union all
  5  select 1, 103, 23845, 23, 2.99, 1 from dual union all
  6  select 1, 104, 23845, 23, 2.99, 1 from dual union all
  7  select 1, 105, 23845, 23, 2.99, 1 from dual
  8  /

Table created.

SQL> create table special_order_system_table (itemid,orderid,order_line,dollars,quantity)
  2  as
  3  select 23845, 23, 1, 8.97, 3 from dual union all
  4  select 23845, 23, 2, 5.98, 2 from dual
  5  /

Table created.

还有查询:

SQL> with t as
  2  ( select tranid
  3         , tranline
  4         , itemid
  5         , orderid
  6         , sum(dollars) over (partition by itemid,orderid order by tranline) running_sum_dollars
  7         , sum(quantity) over (partition by itemid,orderid order by tranline) running_sum_quantity
  8      from store_register_table srt
  9  )
 10  , t2 as
 11  ( select itemid
 12         , orderid
 13         , order_line
 14         , dollars
 15         , quantity
 16         , sum(dollars) over (partition by itemid,orderid order by order_line) running_sum_dollars
 17         , sum(quantity) over (partition by itemid,orderid order by order_line) running_sum_quantity
 18      from special_order_system_table
 19  )
 20  , t3 as
 21  ( select itemid
 22         , orderid
 23         , order_line
 24         , dollars
 25         , quantity
 26         , 1 + lag(running_sum_dollars,1,0) over (partition by itemid,orderid order by order_line) begin_sum_dollars
 27         , running_sum_dollars end_sum_dollars
 28         , 1 + lag(running_sum_quantity,1,0) over (partition by itemid,orderid order by order_line) begin_sum_quantity
 29         , running_sum_quantity end_sum_quantity
 30      from t2
 31  )
 32  select t3.itemid "ItemID"
 33       , t3.orderid "OrderID"
 34       , t3.order_line "Order Line"
 35       , t3.dollars "Dollars"
 36       , t3.quantity "Quantity"
 37       , t.tranid "Tran ID"
 38       , listagg(t.tranline,';') within group (order by t3.itemid,t3.orderid) "Tran Lines"
 39    from t3
 40         inner join t
 41           on (   t.itemid = t3.itemid
 42              and t.orderid = t3.orderid
 43              and t.running_sum_dollars between t3.begin_sum_dollars and t3.end_sum_dollars
 44              and t.running_sum_quantity between t3.begin_sum_quantity and t3.end_sum_quantity
 45              )
 46   group by t3.itemid
 47       , t3.orderid
 48       , t3.order_line
 49       , t3.dollars
 50       , t3.quantity
 51       , t.tranid
 52  /

    ItemID    OrderID Order Line    Dollars   Quantity    Tran ID Tran Lines
---------- ---------- ---------- ---------- ---------- ---------- --------------------
     23845         23          1       8.97          3          1 101;102;103
     23845         23          2       5.98          2          1 104;105

2 rows selected.

问候,
抢。

【讨论】:

  • 谢谢!这是一种有趣的方法。它适用于示例数据。当订单表有两行具有相同的 $ 金额时,就会出现问题。看看这里:sqlfiddle.com/#!4/06e72/1。订单第 2 行仅显示一个匹配的 tran 行,即使应该有五个。我会玩弄这个查询,看看我能不能让它工作。
  • 抱歉 - 我的插入内容有误,项目 ID 不同。但是,如果它将“剩余”2.99 项与订单表上的 14.95 项匹配,这仍然是一个问题。
  • 今晚我会调查一下。
  • 首先,很抱歉回来晚了。我只是忘记了它。但是现在我仔细看了看,我看到您的示例使用了不同的 itemid:23845 与 23843。它认为您的意思是它们是相同的。当你把 23843 改成 23845 的时候,输出和预期的一样。
  • 我最终只是在 PL/SQL 中使用游标按程序进行。数据量不大,如果美元金额不加起来还是有点麻烦的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-21
  • 2018-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多