I have a table with id, cost, and priority columns:
create table a_test_table (id number(4,0), cost number(15,2), priority number(4,0));
insert into a_test_table (id, cost, priority) values (1, 1000000, 10);
insert into a_test_table (id, cost, priority) values (2, 10000000, 9);
insert into a_test_table (id, cost, priority) values (3, 5000000, 8);
insert into a_test_table (id, cost, priority) values (4, 19000000, 7);
insert into a_test_table (id, cost, priority) values (5, 20000000, 6);
insert into a_test_table (id, cost, priority) values (6, 15000000, 5);
insert into a_test_table (id, cost, priority) values (7, 2000000, 4);
insert into a_test_table (id, cost, priority) values (8, 3000000, 3);
insert into a_test_table (id, cost, priority) values (9, 3000000, 2);
insert into a_test_table (id, cost, priority) values (10, 8000000, 1);
commit;
select
id,
to_char(cost, '$999,999,999') as cost,
priority
from
a_test_table;
ID COST PRIORITY ---------- ------------- ---------- 1 $1,000,000 10 2 $10,000,000 9 3 $5,000,000 8 4 $19,000,000 7 5 $20,000,000 6 6 $15,000,000 5 7 $2,000,000 4 8 $3,000,000 3 9 $3,000,000 2 10 $8,000,000 1
Starting with the highest priority (descending), I want to select the rows where the cost adds up to less than (or equal to) $20,000,000.
The result would look like this:
ID COST PRIORITY
---------- ------------- ----------
1 $1,000,000 10
2 $10,000,000 9
3 $5,000,000 8
7 $2,000,000 4
Total: $18,000,000
How can I do this with Oracle SQL?
Here is a way to do it in pure SQL. I won't swear there isn't a better way.
Basically, it uses a recursive common table expression (i.e., WITH costed...) to
compute every possible combination of elements totaling less than 20,000,000.
Then it gets the first full path from that result.
Then, it gets all the rows in that path.
NOTE: the logic assumes that no id is longer than 5 digits. That's the LPAD(id,5,'0') stuff.
WITH costed (id, cost, priority, running_cost, path) as
( SELECT id, cost, priority, cost running_cost, lpad(id,5,'0') path
FROM a_test_table
WHERE cost <= 20000000
UNION ALL
SELECT a.id, a.cost, a.priority, a.cost + costed.running_Cost, costed.path || '|' || lpad(a.id,5,'0')
FROM costed, a_test_table a
WHERE a.priority < costed.priority
AND a.cost + costed.running_cost <= 20000000),
best_path as (
SELECT *
FROM costed c
where not exists ( SELECT 'longer path' FROM costed c2 WHERE c2.path like c.path || '|%' )
order by path
fetch first 1 row only )
SELECT att.*
FROM best_path cross join a_test_table att
WHERE best_path.path like '%' || lpad(att.id,5,'0') || '%'
order by att.priority desc;
+----+----------+----------+ | ID | COST | PRIORITY | +----+----------+----------+ | 1 | 1000000 | 10 | | 2 | 10000000 | 9 | | 3 | 5000000 | 8 | | 7 | 2000000 | 4 | +----+----------+----------+
This version uses MATCH_RECOGNIZE to find all the rows in the best group following the recursive CTE:
WITH costed (id, cost, priority, running_cost, path) as
( SELECT id, cost, priority, cost running_cost, lpad(id,5,'0') path
FROM a_test_table
WHERE cost <= 20000000
UNION ALL
SELECT a.id, a.cost, a.priority, a.cost + costed.running_Cost, costed.path || '|' || lpad(a.id,5,'0')
FROM costed, a_test_table a
WHERE a.priority < costed.priority
AND a.cost + costed.running_cost <= 20000000)
search depth first by priority desc set ord
SELECT id, cost, priority
FROM costed c
MATCH_RECOGNIZE (
ORDER BY path
MEASURES
MATCH_NUMBER() AS mno
ALL ROWS PER MATCH
PATTERN (STRT ADDON*)
DEFINE
ADDON AS ADDON.PATH = PREV(ADDON.PATH) || '|' || LPAD(ADDON.ID,5,'0')
)
WHERE mno = 1
ORDER BY priority DESC;
*Edit: removed use of ROWNUM=1 in anchor part of recursive CTE, since it depended on the arbitrary order in which rows would be returned. I'm surprised no one dinged me on that. *
WITH costed (id, cost, priority, running_cost) as
( SELECT id, cost, priority, cost running_cost
FROM ( SELECT * FROM a_test_table
WHERE cost <= 20000000
ORDER BY priority desc
FETCH FIRST 1 ROW ONLY )
UNION ALL
SELECT a.id, a.cost, a.priority, a.cost + costed.running_Cost
FROM costed CROSS APPLY ( SELECT b.*
FROM a_test_table b
WHERE b.priority < costed.priority
AND b.cost + costed.running_cost <= 20000000
FETCH FIRST 1 ROW ONLY
) a
)
CYCLE id SET is_cycle TO 'Y' DEFAULT 'N'
select id, cost, priority from costed
order by priority desc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With