Update 2: I have resolved the two initial problems. But another one popped out when I made the changes suggested (scroll to "UPDATE").
I use MySQL 5.7. Update: now migrated to MySQL 8.0
I have this table:
+------+
| unit |
+------+
| 1000 |
| 1040 |
| 1555 |
| 1600 |
| 3020 |
| 7000 |
+------+
And I use the following stored procedure to find gaps between the ranges. It is useful, because it has good performance and stops when the size of the gap is the minimum we desired:
CREATE DEFINER=`development`@`%` PROCEDURE `stack_overflow_question`(req_range_start VARCHAR(32), IN req_range_end VARCHAR(32), IN req_quantity INT(11))
BEGIN
SET @range_start = req_range_start;
SET @range_end = req_range_end;
SET @demanded_quantity = req_quantity; # Implementation for calculation of gap size.
SET @quantity_aggregated = 0; # Implementation for calculation of gap size.
SELECT
interval_start,
interval_end,
@quantity := interval_end - interval_start + 1 AS quantity,
@quantity_aggregated := @quantity_aggregated + @quantity AS quantity_aggregated
FROM (
SELECT
@interval_start := @rownum := @rownum + 1 AS interval_start,
@interval_end := IF (@rownum = unit, 0, @rownum := IF ((unit <= @range_end), unit, @range_end + 1)) - 1 AS interval_end
FROM (
SELECT @rownum := (SELECT MIN(@range_start - 1) FROM item)
) AS a
JOIN item
ORDER BY unit
) AS z
WHERE NOT interval_end <= 0 AND interval_start <= @range_end
HAVING @quantity_aggregated <= @demanded_quantity; # This makes the query stop when we have reached the desired size of gap.
END
We have three scenarios: one successful and the other troublesome.
Successful example:
call inventory.stack_overflow_question('1000', '8000', 100); # Works well, we requested 100 and it stopped at the minimum required amount.
Result:
+----------------+--------------+----------+---------------------+
| interval_start | interval_end | quantity | quantity_aggregated |
+----------------+--------------+----------+---------------------+
| 1001 | 1039 | 39 | 39 |
| 1041 | 1554 | 514 | 553 |
+----------------+--------------+----------+---------------------+
Failed example, 1: The last row is not taken into account. FIXED
call inventory.stack_overflow_question('1000', '9000', 10000); # Produces interval_end to be 6999, but should be 9000 because we are requesting 10,000 gap size and should assume 9000 is the end.
Result:
+----------------+--------------+----------+---------------------+
| interval_start | interval_end | quantity | quantity_aggregated |
+----------------+--------------+----------+---------------------+
| 1001 | 1039 | 39 | 39 |
| 1041 | 1554 | 514 | 553 |
| 1556 | 1599 | 44 | 597 |
| 1601 | 3019 | 1419 | 2016 |
| 3021 | 6999 | 3979 | 5995 |
+----------------+--------------+----------+---------------------+
Desired result:
+----------------+--------------+----------+---------------------+
| interval_start | interval_end | quantity | quantity_aggregated |
+----------------+--------------+----------+---------------------+
| 1001 | 1039 | 39 | 39 |
| 1041 | 1554 | 514 | 553 |
| 1556 | 1599 | 44 | 597 |
| 1601 | 3019 | 1419 | 2016 |
| 3021 | 6999 | 3979 | 5995 |
| 7001 | 9000 | 2000 | 7995 | < additional row showing the end of the range.
+----------------+--------------+----------+---------------------+
Failed example: FIXED
call inventory.stack_overflow_question('1000', '1000', 10000); # I don't even know what is happening here. Since we want to find between 1000 and 1000 and there is no gap, it should return nothing.
Result:
+----------------+--------------+----------+---------------------+
| interval_start | interval_end | quantity | quantity_aggregated |
+----------------+--------------+----------+---------------------+
| 1001 | 1000 | 0 | 0 |
| 1002 | 1000 | -1 | -1 |
| 1002 | 1000 | -1 | -2 |
| 1002 | 1000 | -1 | -3 |
| 1002 | 1000 | -1 | -4 |
+----------------+--------------+----------+---------------------+
FIXED: so I fixed this one adding AND interval_start <= @range_end
to the WHERE clause in the SQL script.
So I simply wonder how to make it work properly and overcome the failed scenarios. Any ideas?
UPDATE AFTER APPLYING PROPOSED SOLUTION:
After making the upgrade to MySQL 8.0, applied Anonymous' solution, and it fixed the problem that the last interval will not fill up to the end... but now I have problems because the first interval is not being started from the range_start as supposed.
Is driving me crazy how much time such a ridiculously simple solution that should be easy to write is taking so much time. I beg for help!!
Scenario:
Now we have this table:
+-------------+
| unit |
+-------------+
| 3079 |
| 3130 |
| 3185 |
+-------------+
And the solution proposed by Anonymous is implemented:
SET @range_start = 3000;
SET @range_end = 6000;
SELECT
unit + 1 AS interval_start,
LEAD(unit, 1, @range_end + 1) OVER (ORDER BY unit) - 1 AS interval_end,
((LEAD(unit, 1, @range_end + 1) OVER (ORDER BY unit) - 1) - (unit + 1)) + 1 AS quantity
FROM item
WHERE (
unit <= @range_end
)
ORDER BY unit
Problem: The first interval should count from the range_start, this is: 3000.
+----------------+--------------+----------+
| interval_start | interval_end | quantity |
+----------------+--------------+----------+
| 3080 | 3129 | 50 |
| 3131 | 3184 | 54 |
| 3186 | 6000 | 2815 |
+----------------+--------------+----------+
Expected output:
+----------------+--------------+----------+
| interval_start | interval_end | quantity |
+----------------+--------------+----------+
| 3000 | 3078 | 79 | < I can't manage to recreate this.
| 3080 | 3129 | 50 |
| 3131 | 3184 | 54 |
| 3186 | 6000 | 2815 |
+----------------+--------------+----------+