Reputation: 43
I have an Oracle 11 database table that keeps track of the status of inventory items for each department.
ITEM_NUMBER DEPT_NO STATUS ----------- --------- --------- 1 AAA OK 2 AAA OK 3 AAA MISSING 4 BBB OK 5 BBB OK 6 AAA OK 7 AAA OK
I would like to create a view that shows each range of items, by department, and the status. If any items in the range are MISSING, the status should be MISSING, otherwise, it should be OK.
In the above example, the view output should be:
START END DEPT_NO STATUS ------ ----- ------- ------------ 1 3 AAA MISSING 4 5 BBB OK 6 7 AAA OK
This would be a simple thing to do if the departments only had one group of records - however, the same department can show up in different ranges, so doing GROUP BYs with MIN/MAX doesn't work because it ends up summarizing the two ranges for department AAA as one:
select dept_no, min(item_number), max(item_number), min(status) from inventory group by dept_no START END DEPT_NO STATUS ------ ----- ------- ------------ 1 7 AAA MISSING 4 5 BBB OK
Can this be done using a database view or is it too complex?
Upvotes: 4
Views: 286
Reputation: 21542
Ok, this should work
ps: end and start are reserved words and I could not use them. I just added a letter, but this is still a clear result...
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
Fiddle: http://sqlfiddle.com/#!4/944ff3/17
Upvotes: 1
Reputation: 1270653
You can solve this using window functions, but in to levels. The first is to use lag()
to determine where a new "group" of departments begins. The second is a cumulative sum of this value. The cumulative sum works as a grouping id, which can then be used for aggregation.
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;
Upvotes: 1
Reputation: 36127
Another alternative (works on 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
Upvotes: 1
Reputation: 13108
If this can be done using a database view I think it would be very complex. One option you could look into would be to try to create an inner query which is the same table joined to itself on item_number = item_number+1 and add a column that detects where a.item_number != b.item number to determine group boundaries and then use this group boundaries column as the group by in the outer query.
However, the problem is clearly better suited to a procedural approach looping through one row at a time. So I would recommend doing this as a pl/sql procedure instead if you can.
Upvotes: 0