SQL interview question
Asked Answered
L

8

12

I got following question on an interview: Given a table of natural numbers with some missing ones, provide output of two tables, beginning of number gap in first table and ending in second. Example:

 ____    ________
|    |   |   |   |
| 1  |   | 3 | 3 |
| 2  |   | 6 | 7 |
| 4  |   | 10| 12|
| 5  |   |___|___|
| 8  |
| 9  |
| 13 |
|____|
Luckey answered 24/9, 2010 at 15:28 Comment(3)
Hmm... You could probably do this easily with analytic functions like lag and lead (maybe when I have time at lunch)... but that would be specific to Oracle (or others that support those functions). Was this to be a generic solution that can run on any RDBMS, or are you allowed to assume a specific implementation?Pulpwood
I suppose it had to be something generic, because other questions, which was on programming, were language-agnostic.Luckey
I think you should be able to do this with two exists statements and a self comparison to populate a temp table (one exists to see when the next number isn't in the table, another for when the preceding number isn't).Burck
N
7

While this is pretty much the same as Phil Sandler's answer, this should return two separate tables (and I think it looks cleaner) (it works in SQL Server, at least):

DECLARE @temp TABLE (num int)
INSERT INTO @temp VALUES (1),(2),(4),(5),(8),(9),(13)

DECLARE @min INT, @max INT
SELECT @min = MIN(num), @max = MAX(num) FROM @temp

SELECT t.num + 1 AS range_start
    FROM @temp t
    LEFT JOIN @temp t2 ON t.num + 1 = t2.num
    WHERE t.num < @max AND t2.num IS NULL

SELECT t.num - 1 AS range_end
    FROM @temp t
    LEFT JOIN @temp t2 ON t.num - 1 = t2.num
    WHERE t.num > @min AND t2.num IS NULL
Notarize answered 24/9, 2010 at 16:54 Comment(0)
J
2

This is SQL Server syntax:

CREATE TABLE #temp (columnA int)

INSERT INTO #temp VALUES(1)
INSERT INTO #temp VALUES(2)
INSERT INTO #temp VALUES(4)
INSERT INTO #temp VALUES(5)
INSERT INTO #temp VALUES(8)
INSERT INTO #temp VALUES(9)
INSERT INTO #temp VALUES(13)

SELECT 
    t1.columnA - 1
FROM 
    #temp t1
    LEFT JOIN #temp t2 ON t1.columnA = t2.ColumnA + 1
WHERE 
    t2.ColumnA IS NULL
    AND t1.ColumnA != (SELECT MIN(ColumnA) from #temp)  

SELECT 
    t1.columnA + 1
FROM 
    #temp t1
    LEFT JOIN #temp t2 ON t1.columnA = t2.ColumnA - 1
WHERE 
    t2.ColumnA IS NULL  
    AND t1.ColumnA != (SELECT MAX(ColumnA) from #temp)  

DROP table #temp
Jackass answered 24/9, 2010 at 15:49 Comment(5)
Um, how does this produce the two-column result...?Agateware
We came up with the same concept (but slightly different implementation), so you get an upvote from me.Notarize
@egrunin: the questions says output of two tables, not two columns.Jackass
@Phil: you're right: the illustration showed one result table, the text showed two (and some of the other answers read it as it did as well). Unfortunately SO won't let me retract my downvote unless the answer gets edited. Sorry.Agateware
I'm sorry about text/illustration contradiction, that's my fault. The question itself on the interview had that, it was written "tables", but illustrated ONE column with two values separated by comma. Anyway, thanks for the answers, it's really helpful.Luckey
T
2

Itzik Ben Gan writes a lot about these "gaps and islands" problems. His row_number solution to this is

WITH C AS
(
SELECT N, ROW_NUMBER() OVER (ORDER BY N) AS RN
FROM t
)
SELECT Cur.N+1,Nxt.N-1
FROM C AS Cur 
JOIN C AS Nxt ON Nxt.RN = Cur.RN+1
WHERE Nxt.N-Cur.N>1

And a solution without row_number from the same source.

SELECT N+1 AS start_range,
(SELECT MIN(B.N) FROM t AS B WHERE B.N > A.N)-1 AS end_range
FROM t AS A
WHERE NOT EXISTS(SELECT * FROM t AS B WHERE B.N = A.N+1)
AND N< (SELECT MAX(N) FROM t)
Tatia answered 24/9, 2010 at 16:30 Comment(0)
R
2

This works without DB Specific SQL and it could probably be made a little cleaner but it does work

EDIT: You can see this working at on this Query at StackExchange Data Explorer

SELECT low,high FROM 

(

SELECT col1, low 

FROM
(Select n1.col1 col1, min(n2.col1) + 1 low
 from numbers n1
inner join numbers n2
on n1.col1 < n2.col1 

Group by n1.col1) t
WHERE t.low not in (SELECT col1 FROM NUMBERS)
and t.low < (Select MAX(col1) from numbers) 
) t

INNER JOIN 
(

SELECT col1 - 1 col1, high
 FROM
(Select n1.col1 col1 , min(n2.col1) - 1 high
 from numbers n1
inner join numbers n2
on n1.col1 < n2.col1 

Group by n1.col1) t
WHERE t.high not in (SELECT col1 FROM NUMBERS) 
) t2
ON t.col1 = t2.col1
River answered 24/9, 2010 at 16:41 Comment(1)
Didn't know about Data Explorer!!Agateware
A
1

Something like this:

SELECT col1, col2 FROM
(
    SELECT x + 1 as col1, 
        ROW_NUMBER() OVER(ORDER BY x) AS 'rownum'  
    FROM tbl y 
    WHERE NOT EXISTS (SELECT x FROM tbl z WHERE z.x = y.x + 1) 
        AND x <> (SELECT MAX(x) FROM tbl)
) a
INNER JOIN
(
    SELECT x - 1 as col2,
        ROW_NUMBER() OVER(ORDER BY x) AS 'rownum'  
    FROM tbl y 
    WHERE NOT EXISTS (SELECT x FROM tbl z WHERE z.x = y.x - 1) 
        AND x <> (SELECT MIN(x) FROM tbl)
) b
ON a.rownum = b.rownum

The "rownum" syntax will be different for different DBMS. The above might work for SQL Server, but I haven't tested it.

As one of the comments pointed out, many DBMS's have analytics that will make this easier.

Agateware answered 24/9, 2010 at 15:38 Comment(2)
For first and last, you could add something for MAX and MIN values of the column.Burck
@Phil: not anymore :) and if you want a two-table solution, just remove the row-number clauses and the outer SELECTAgateware
V
0

You can use Lag function to access previous row:

create table #a (n int)

insert #a values(1)
insert #a values(2)
insert #a values(4)
insert #a values(5)
insert #a values(8)
insert #a values(9)
insert #a values(13)

select  prev + 1, n - 1 from
(select lag(n) over(order by n) as prev, n
from    #a) a
where   prev < n - 1

Result:

|3  |3  |

|6  |7  |

|10 |12 |
Vent answered 29/8, 2017 at 15:5 Comment(0)
U
0

SQL Fiddle Setup and Solution

1. Step1

From the list of IDs get the current id and all the next ids available

select l1.id curr_id,l2.id next_id from
id_list l1,id_List l2
where l1.id < l2.id;

2. Step 2

From the above list we will see all the combinations but filter only the one combination per current ID with immediate smallest next ID and for that, get min current ID and min next ID for each current ID. Use group by per current ID

with id_combinations as
(
 select l1.id curr_id,l2.id next_id from
 id_list l1,id_List l2
 where l1.id < l2.id
)
select min(curr_id)+1 missing_id_start -- Need to add 1 from current available id
       ,min(next_id)-1 missing_id_end -- Need to subtract 1 from next available id 
from id_combinations
group by curr_id
having min(curr_id)+1 < min(next_id) -- Filter to get only the missing ranges
Uptotheminute answered 8/3, 2019 at 23:56 Comment(2)
Welcome to Stack Overflow, please explain why this answers the question.Agonized
Code only answers are discouraged. Please add some explanation as to how this solves the problem, or how this differs from the existing answers. From ReviewFoin
B
0
create table sequence_table(seq integer);

insert into sequence_table values(1);
insert into sequence_table values(2);
insert into sequence_table values(4);
insert into sequence_table values(5);
insert into sequence_table values(8);
insert into sequence_table values(9);
insert into sequence_table values(13);
insert into sequence_table values(14);
insert into sequence_table values(15);

with tmp_next_seq as
(
  select s.seq,
       (lead(s.seq) over(order by s.seq) - s.seq) next_seq_gap
  from sequence_table s
)
select t.seq+1 missing_seq_start,
       t.seq+t.next_seq_gap-1 missing_seq_end
  from tmp_next_seq t
where next_seq_gap > 1;
Beria answered 6/5, 2023 at 20:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.