Reputation: 47
I found this question on leetcode:
Given a table name "numbers" with 1 column name "id" like this: 2, 3, 5, 1, 4, 2, 7, 8 Write an SQL script to find out the ranges of numbers that are monotonically increasing. Answer: 2-5, 1-4, 2-8
My solution is what follows, which is quite long with a lot of subqueries since I'm new with SQL, I'm only comfortable with select, from, where, group by
. Could you please let me know if my query is correct? I believe that there exists a simpler query than my solution, but for the moment I don't know how to do it. Could you please let me know how?
with x as
select current, increasing as a, lead(increasing,1,0) as b
from
(
(select current, (case when current > before then 1 else 0 end) as increasing
from
(select id as current, lag(id,1,0) as before
from table numbers)
)
)
select t1.start, t2.end
(select current as start, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
from x
where a=0 and b=1) t1
inner join
(select current as end, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
from x
where a=1 and b=0) t2
on t1.numrow = t2.numrow
Thanks.
Upvotes: 0
Views: 628
Reputation: 175586
I believe that there exists a simpler query than my solution, but for the moment I don't know how to do it
This is gaps-and-islands class problem.
Please note that tables are not sorted by design. You need some kind of column to determine index. ORDER BY (SELECT NULL)
is probably to satisfy T-SQL but it is not safe.
There are multiple ways to solve it. One of my favourites is MATCH_RECOGNIZE:
SELECT f, l
FROM t
MATCH_RECOGNIZE (
ORDER BY id
MEASURES first(c) AS f,
last(c) AS l
ONE ROW PER MATCH
PATTERN( l+ ge* )
DEFINE l AS c < NEXT(c) OR NEXT(c) IS NULL,
ge AS c >= NEXT(c)
) mr;
Upvotes: 0
Reputation: 1269503
In order to answer your question, your data needs to have an ordering column. SQL tables represent unordered (multi)sets, so there is no ordering without such a column.
Use lag()
to determine where a monotonically increasing group starts. Then do a cumulative sum to assign a grouping to the group. And finally aggregation:
select min(id), max(id)
from (select n.*,
sum(case when prev_id < id then 0 else 1 end) over (order by <ordering col>) as grp
from (select n.*,
lag(id) over (order by <ordering col>) as prev_n
from numbers n
) n
) n
group by grp;
Here is a db<>fiddle.
Upvotes: 1