【问题标题】:How can I summarize a range of rows with duplicate values in them in Oracle?如何在 Oracle 中汇总具有重复值的一系列行?
【发布时间】:2013-07-24 23:00:25
【问题描述】:

我有一个 Oracle 11 数据库表,用于跟踪每个部门的库存项目状态。

ITEM_NUMBER DEPT_NO 状态 ------------ --------- --------- 1 AAA 好的 2 AAA OK 3 AAA 缺失 4 血脑屏障正常 5 BBB 正常 6 AAA OK 7 AAA OK

我想创建一个视图,按部门和状态显示每个项目范围。如果范围内的任何项目为 MISSING,则状态应为 MISSING,否则应为 OK。

在上面的例子中,视图输出应该是:

START END DEPT_NO STATUS ------ ----- -------- ------------ 1 3 AAA 缺失 4 5 BBB 正常 6 7 AAA OK

如果部门只有一组记录,这将是一件简单的事情 - 但是,同一个部门可以显示在不同的范围内,因此使用 MIN/MAX 进行 GROUP BY 不起作用,因为它最终会汇总部门 AAA 的两个范围为一个:

选择部门编号,最小(项目编号),最大(项目编号),最小(状态) 从库存 按部门编号分组 START END DEPT_NO STATUS ------ ----- -------- ------------ 1 7 AAA 缺失 4 5 BBB 正常

这可以使用数据库视图来完成还是太复杂了?

【问题讨论】:

  • 这个问题很适合面试:p

标签: oracle plsql


【解决方案1】:

您可以使用窗口函数来解决这个问题,但要在关卡中。第一个是使用lag() 来确定一个新的部门“组”从哪里开始。第二个是这个值的累积和。累积总和用作分组 id,然后可用于聚合。

select dept_no, min(item_number), max(item_number),
       (case when sum(case when status = 'MISSING' then 1 else 0 end) > 0
             then 'Missing'
             else 'Ok'
        end)
from (select i.*, sum(GroupStart) over (order by item_number) as Grouping
      from (select i.*,
                   (case when dept_no = lag(dept_no) over (order by item_number)
                         then 0 else 1
                    end) as GroupStart
            from inventory i
           ) i
     ) i
group by dept_no, grouping;

【讨论】:

  • 很好的解决方案 - 它还可以处理项目编号之间存在差距的情况(例如,项目编号从 11 变为 13,缺少 #12)。
【解决方案2】:

另一种选择(适用于 11g)

WITH qry( item_number, dept_no, status, range_id ) as (
    select item_number, dept_no, status, 1 range_id
    from inventory where item_number = 1
    UNION ALL
    select src.item_number, src.dept_no, src.status,  
         case when src.dept_no <> qry.dept_no
              then qry.range_id + 1 else qry.range_id
         end
    from inventory src
    join qry on src.item_number = qry.item_number + 1
)
SELECT range_id, dept_no, min(item_number), max(item_number), min(status)
from qry
group by range_id, dept_no
order by 1
;

  RANGE_ID DEPT_NO MIN(ITEM_NUMBER) MAX(ITEM_NUMBER) MIN(STATUS)
---------- ------- ---------------- ---------------- -----------
         1 AAA                    1                3 MISSING     
         2 BBB                    4                5 OK          
         3 AAA                    6                7 OK    

【讨论】:

  • 很好地使用了 CTE。我不知道我们可以通过它自己的名字引用 CTE... 现在我正在阅读一些关于它的内容,我发现这是一个非常令人印象深刻的工具:morganslibrary.org/reference/with.html
【解决方案3】:

好的,应该可以了

ps: end 和 start 是保留字,我不能使用它们。我只是加了一个字母,但这仍然是一个明确的结果...

SELECT
    i.sstart,
    i.eend,
    i.dept_no,
    MIN(inventory.status) AS status
FROM (
    SELECT 
        DECODE(r, 1, 1, (LAG(max_range_item, 1) OVER(ORDER BY r) + 1)  ) AS sstart,
        max_range_item AS eend,
        dept_no
    FROM (
        SELECT
            rownum AS r,
            item_number AS max_range_item,
            dept_no
        FROM (
                SELECT 
                    item_number, 
                    dept_no, 
                    LEAD(dept_no, 1) OVER (ORDER BY item_number) AS next_dept_no
                FROM
                    inventory
                ORDER BY
                    item_number ASC
              ) i2
        WHERE
            dept_no != next_dept_no
        OR  next_dept_no IS NULL
        ORDER BY
            item_number ASC
        ) inv
    ) i
        JOIN inventory ON inventory.item_number BETWEEN i.sstart AND i.eend
GROUP BY
    i.sstart,
    i.eend,
    i.dept_no
ORDER BY
    i.sstart ASC

小提琴:http://sqlfiddle.com/#!4/944ff3/17

【讨论】:

  • 为了进一步的教学参考,我不得不使用这么多子查询,因为分析函数不能用于 where 子句。解决方案是过滤包含函数的子查询的结果(本例中为 LEAD 和 LAG)
【解决方案4】:

如果这可以使用数据库视图来完成,我认为这将非常复杂。您可以考虑的一个选项是尝试创建一个内部查询,该查询是在 item_number = item_number+1 上连接到自身的同一个表,并添加一个列来检测 a.item_number != b.item number 的位置以确定组边界和然后将此组边界列用作外部查询中的分组依据。

但是,这个问题显然更适合一次循环遍历一行的程序方法。因此,如果可以的话,我建议将其作为 pl/sql 过程来执行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多