how to select multiple max values from column in sql server? - sql

I have following two tables TBLSession and TBLStudentFeeRecord having following sample datasets
TBLSession:
SessionId SessionName SessionStartMonth SessionEndMonth
1 2018-2019 2018-03-24 2019-02-24
2 2019-2020 2019-01-30 2019-12-30
3 2020-2021 2020-01-30 2021-12-30
TBLStudentFeeRecord:
StudentId SessionId TutionFee BranchId ClassId SectionId
1001 1 1000 1 1 1
1001 2 2000 1 3 1
1001 3 1000 2 2 1
Now,what i am trying to achieve is to select two maximum sessions TutionFee of selected StudentId. I can use max(columnName) to get one maximum value from that particular column. Now how can i get two maximum sessions?
Following dataset is required after querying these table
ResultDataSet:
StudentId SessionId TutionFee SessionName
1001 2 2000 2019-2020
1001 3 1000 2020-2021
What will be the query to achieve above dataset?

You can use SQL Row_Number function with Partition By clause if you want to get the 2 session info per student
Otherwise, use Row_number() function without Partition By clause
You can find explanations for two alternatives in the following SQL CTE statements
;with cte as (
select *,
-- top 2 session per student
-- rn = ROW_NUMBER() over (partition by studentid order by sessionid desc)
-- top 2 sessions
rn = ROW_NUMBER() over (order by sessionid desc)
from TBLStudentFeeRecord
)
select
*
from cte
inner join TBLSession on TBLSession.sessionid = cte.sessionid
where rn <= 2
The output is as follows
You can modify the select list according to your requirements

Since you haven't posted your tries, so try something like this:
Pseudo query:
select top 2 from (select TutionFee from TBLStudentFeeRecord
where StudentId = 1001
order by TutionFee desc )

You could use a subselect Top 2 on the max TutionFee and SessionId
select a.* , t.TutionFee
from TBLSession a
inner JOIN (
select TOP 2 studentID, TutionFee, SessionId
from TBLStudentFeeRecord
where StudentId = 1001
order TutionFee desc, SessionID desc
) t on t.SessionId = a.SessionId

Try this:
SELECT StudentId ,SessionId,TutionFee,SessionName
FROM(
SELECT TSF.StudentId ,TSF.SessionId,TSF.TutionFee,TS.SessionName
,ROW_NUMBER() OVER(PARTITION BY TSF.TutionFee ORDER BY SessionId DESC)RN
FROM TBLStudentFeeRecord TSF
INNER JOIN TBLSession TS ON TS.SessionId=TSF.SessionId
)D
WHERE RN=1

this should work
select TBLStudentFeeRecord.StudentId, TBLStudentFeeRecord.SessionId, TBLStudentFeeRecord.TutionFee, TBLStudentFeeRecord.SessionName
from TBLStudentFeeRecord
inner join TBLSession on TBLSession.SessionId = TBLStudentFeeRecord.SessionId
where TBLStudentFeeRecord.StudentId = 1000
ORDER BY TBLStudentFeeRecord.TutionFee DESC
LIMIT 2

In general query languages are powerful if you face any difficult or any complex scenario just use stored procedure
Two max..........................................
CREATE PROCEDURE twoMaxFrom()
BEGIN
DECLARE max1 DOUBLE;
DECLARE max2 DOUBLE;
DECLARE emp_cursor CURSOR FOR SELECT
TutionFee
FROM TBLStudentFeeRecord;
-- 2. Declare NOT FOUND handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
-- 3. Open the cursor
OPEN emp_cursor;
L: LOOP
-- 4. Fetch next element
FETCH emp_cursor
INTO TutionFee;
-- Handler will set finished = 1 if cursor is empty
IF finished = 1
THEN
LEAVE L;
END IF;
SET max1 = 0;
SET max2 = 0;
IF max1 > max2
THEN
SET max1 = TutionFee;
SET max2 = max1;
END IF;
END LOOP;
-- 5. Close cursor when done
CLOSE emp_cursor;
SELECT max1;
SELECT max2;
END;

You can get it by using join and TOP
SELECT TOP 2 StudentId
,t1.SessionId
,TutionFee
,SessionName
FROM TBLSession AS t1
INNER JOIN TBLStudentFeeRecord AS t2 ON t1.SessionId = t2.SessionId
WHERE t2.StudentId = 1001
ORDER BY t1.SessionId DESC

Related

Paging in SQL with no break on primary key

I am new to Stackoverflow. Please forgive me if I have made some mistake.
I have following data in SQL.
Row No. Customer No. Customer Name
------------------------------------------------------------
1 1234 ABCD
2 1234 ABCD
3 1234 ABCD
4 6789 WXYZ
5 6789 WXYZ
6 3456 OPQR
7 4567 JKLM
I need to page the above data with following constraint
Page size : 4 records
If customer no. is splitting between two pages then new customer no. should go on the next page.
output desired:
Paging with 4 records each page
1st Page
Row No. Customer No. Customer Name
------------------------------------------------------------
1 1234 ABCD
2 1234 ABCD
3 1234 ABCD
2nd Page
Row No. Customer No. Customer Name
------------------------------------------------------------
4 6789 WXYZ
5 6789 WXYZ
6 3456 OPQR
7 4567 JKLM
Please help.
You can read page size +1 records using any limiting, then if the last record and the record before the last has the same Customer Name just filter out the customer using HAVING.
Could be tricky and depends on DB you use and could be easily done on presentation layer
Declare #PageNumber INT = 1, #PageSize INT = 4
;with cte1(RowNo,CustomerNo,CustomerName)
AS
(
Select 1,1234,'ABCD' union all
Select 2,1234,'ABCD' union all
Select 3,1234,'ABCD' union all
Select 4,6789,'WXYZ' union all
Select 5,6789,'WXYZ' union all
Select 6,3456,'OPQR' union all
Select 7,4567,'JKLM'
)
select * from cte1 order by RowNo
OFFSET #PageSize * (#PageNumber - 1) ROWS
FETCH NEXT #PageSize ROWS ONLY
This can be done and not to complicated using recursive CTE.
For sample data, I have used query from other answer
WITH cte1(RowNo,CustomerNo,CustomerName)
AS
(
SELECT 1,1234,'ABCD' UNION ALL
SELECT 2,1234,'ABCD' UNION ALL
SELECT 3,1234,'ABCD' UNION ALL
SELECT 4,6789,'WXYZ' UNION ALL
SELECT 5,6789,'WXYZ' UNION ALL
SELECT 6,3456,'OPQR' UNION ALL
SELECT 7,4567,'JKLM'
)
SELECT * INTO #table FROM cte1
I'l add one more row with reapaing user to split this to 3rd page
INSERT #table (RowNo, CustomerNo, CustomerName) VALUES (8,3456,'OPQR')
And here is the solution. I've put some comments to explain parts
WITH CTE_Source AS
(
--I use this to add RN column simply beacuse I can't trust that rowNo column will have no gaps
SELECT *
, ROW_NUMBER() OVER (ORDER BY RowNo) RN
FROM #table t
)
, CTE_R AS
(
--First part is select of first row
SELECT s.RowNo
, s.CustomerNo
, s.CustomerName
, s.RN
, 1 AS Grp --this is a current group of rows
, 1 AS Cnt --counter of how many rows group have
FROM CTE_Source s WHERE RN = 1
UNION ALL
--subsequent select is for next row
SELECT
s.RowNo
, s.CustomerNo
, s.CustomerName
, s.RN
-- increase group when different customer
, CASE WHEN s.CustomerNo = r.CustomerNo THEN r.Grp ELSE r.Grp+Cnt END
-- increase counter when same customer
, CASE WHEN s.CustomerNo = r.CustomerNo THEN r.Cnt + 1 ELSE 1 END
FROM CTE_R r
INNER JOIN CTE_Source s ON s.RN = r.Rn + 1
)
, CTE_Paging AS
(
SELECT *
, CEILING((r.Grp + r.Cnt) / 4.) AS Page -- replace 4 with your page size
FROM CTE_R r
)
SELECT * FROM CTE_Paging --Just add WHERE Page = if you want specific page
OPTION (MAXRECURSION 0) -- for unlimited recursion if you have more than 100 rows

Adding first two rows result as a second row then addition of first three rows result as a third row and so on

I have a table in sql serevr,which has one column and its storing integer values.
Ex : ColumnData
100
150
20
25
300
Now by using this data i want the result as shown below.
columndata NewColumn
100 100
150 250
20 270
25 295
300 595
so in the output newcolumn is added by the logic i.e first row data as firstrow,then first two rows addition result as appears in second row,then first three rows addition result as appears in third row like so on...
could any one please provide me the query how to get my result.
Thanks In Advance,
Phani Kumar.
Assuming that you have a column that you can order the data by then you can compute a running total by either using a windowed aggregate function (this works in SQL Server 2012+) or a self join (which works in any version). If you don't have any column to order by then it can't be done in a deterministic way at all.
-- sample table:
create table t (id int identity(1,1), ColumnData int)
insert t values (100),(150),(20),(25),(300)
-- query 1 using windowed aggregate
select ColumnData, sum(ColumnData) over (order by id) as NewColumn
from t order by id
-- query 2 using self-join
select t1.ColumnData, sum(t2.ColumnData) as NewColumn
from t t1
join t t2 on t2.id <= t1.id
group by t1.id, t1.ColumnData
order by t1.id
Sample SQL Fiddle
You need to use PL SQL to to do this.
Alter the table to have a new field id to to sort and value2 having final result.
DECLARE
l_last_sum INTEGER := 0;
CURSOR test_cur
IS
SELECT id,value
FROM test
ORDER BY id ASC;
l_test test_cur%ROWTYPE;
BEGIN
OPEN test_cur;
LOOP
FETCH test_cur INTO l_test;
EXIT WHEN test_cur%NOTFOUND;
l_last_sum:=l_last_sum+l_test.value;
update test set value2=l_last_sum where id=l_test.id;
END LOOP;
CLOSE test_cur;
END;
SQL> select * from test;
ID VALUE VALUE2
---------- ---------- ----------
1 100 100
2 25 125
3 40 165
with sal as(
select a.empid,salry,row_number() over(order by empid) rn from empmaster a)
select a.empid,a.salry,b.salry,a.salry+b.salry
from sal a
left outer join sal b on a.rn = b.rn-1

SQL Group By Certain Amounts

I have an order table which has the customer id and order amount. I want to join these orders but joined orders cannot exceed a certain amount. An example below:
Let's say the maximum amount is 33 pallets and I have a table like this:
Order ID Client ID Amount
1 100001 10
2 100001 22
3 100001 13
4 100001 33
5 100001 1
6 100001 5
7 100001 6
The result should be:
Order ID Client ID Amount Joined ID Joined Amount
1 100001 10 100001A 32
2 100001 22 100001A 32
3 100001 13 100001B 13
4 100001 33 100001C 33
5 100001 1 100001D 12
6 100001 5 100001D 12
7 100001 6 100001D 12
Here, if we can also come up with a way to ad orders numbered 5,6,7 to joined order 10001B it would be great. But even this solution will be enough.
I have a few ideas on how to solve this but I couldn't really come up with a working solution. I'll be handling around 2000 Order Ids like this, so also I don't want this to be a slow operation. I'm using SQL Server 2014
you can find proposed solution (sql definition) with help of recursive CTE here: http://sqlfiddle.com/#!6/285c16/45
basicaly CTE iterates ordered list (by clientID, orderID) and evaluate if summed amount is not over 33.
i have added next clientID to mock data, to test correct subcount criteria evaluation.
here is query to obtain results:
-- prepare numbering for iteration
with orders_nr
as
(
select row_number() over(order by clientID, id) as [nr],
o.*
from orders o
)
,
-- prepare sum totals
re
as
(
select id, amount, amount as amount_total ,o.[nr] as nr,
clientID
from orders_nr o
where o.[nr]=1
UNION ALL
select o.id, o.amount,
CASE WHEN o.clientID <> r.clientID then o.amount
ELSE o.amount+ r.amount_total END,
o.[nr] as nr, o.clientID
from orders_nr o join re r
on (o.[nr]=r.[nr]+1)
)
,
-- iterate total - evaluate current criteria (<=33)
re2 as
(
select re.id, re.amount, re.amount_total,
re.[nr] as [group], re.[nr], re.clientID
from re
where re.[nr]=1
UNION ALL
select r.id, r.amount,
CASE WHEN r.amount+re2.amount_total >33
OR r.clientID<>re2.clientID
then r.amount ELSE re2.amount_total+r.amount END
as amount_total,
CASE WHEN r.amount+re2.amount_total >33
OR r.clientID<>re2.clientID THEN
r.[nr] ELSE re2.[group] END as [group], r.[nr], r.clientID
from re r join re2
on (r.[nr]=re2.[nr]+1 )
)
, group_total
AS
(
select [group], clientID, max(amount_total) as total
FROM re2
group by [group], clientID
),
result
as
(
select
r.id, r.clientID, r.amount,
cast(r.clientid as varchar(20))
+'-'+char(64+cast(
dense_rank()
over( partition by r.clientID
order by r.[clientID], r.[group])
as varchar(3))) as joinedID
, gt.total as joinedAmount
from re2 as r join group_total gt
on (r.clientID=gt.clientID AND r.[group]=gt.[group])
)
select * from result
Not certain if I'm understanding the question correctly, but you might try
select [Client ID], [Joined ID], sum([Amount]) as Total_Amount
from [table_name]
group by [Client ID], [Joined ID]
having sum([Amount]) <= 33
It leaves off the Order ID, but since it looks to be unique, you can't use it in a group by.
Edit was to add the having clause in the query to say we can't have something that adds up to more than 33.
I tried to solve with simple selects and without using an explicit cursor but it was a little hard in that way.
I've solved it and got exactly what you wanted with:
a TempTable, a cursor, a counter for checking the sum of sequent amounts, CHAR() function to generate letters; I calculated the values and inserted into temp table finally updated the temp table, following is what I tried and the DEMO IS HERE.
create table #tbl_name
(OrderID int,
ClientID int,
Amount int,
joinedId varchar(15) ,
joinedAmount int)
insert #tbl_name(OrderID,ClientID,Amount)
select OrderID,ClientID,Amount from tbl_name
declare cr cursor for
select orderId,
clientId,
amount
from tbl_name
order by OrderId
declare #summedAmount int,
#orderId int,
#clientId int,
#amount int,
#counter int
set #summedAmount=0
set #counter=65
open cr
fetch from cr into #orderId,#clientId,#amount
while (##fetch_status=0)
begin
if (#amount + #summedAmount < 33)
begin
set #summedAmount=#summedAmount+#amount
update #tbl_name
set joinedId=cast(#ClientId as varchar(10))+char(#counter),
joinedAmount=#summedAmount
where orderId=#orderId
end
else if (#amount + #summedAmount >33)
begin
set #counter=#counter+1
set #summedAmount=#amount
update #tbl_name
set joinedId=cast(#ClientId as varchar(10))+char(#counter),
joinedAmount=#Amount
where orderId=#orderId
end
fetch from cr into #orderId,#clientId,#amount
end
close cr
deallocate cr
go
with CTE as
(
select JoinedId, max(joinedAmount) mx
from #tbl_name
group by JoinedId
)
update #tbl_name
set joinedAmount = CTE.mx
from #tbl_name
join CTE on #tbl_name.JoinedId=CTE.JoinedId
select * from #tbl_name
drop table #tbl_name

How to get in one query an item and another item having one of its value nearest of the former one?

Imagine I have the following table :
ID || Order
-----------
1 || 1
2 || 2
3 || 5
4 || 20
5 || 100
6 || 4000
(no specific rule applies to the order value).
I want to "move up" ou "move down" items by swapping order values.
Ex: a call to MoveItemUp(4) will results in this new table values :
ID || Order
-----------
1 || 1
2 || 2
3 || 20 <-- swapped order value
4 || 5 <-- swapped order value
5 || 100
6 || 4000
I want to do this in a single query, but I was not yet successful.
The following query works if items order are sequential, with no "hole" (steps of 1 :)
UPDATE dbo.ITEMS
set ORDER = case when c.ORDER = c2.ORDER then c.ORDER +1 else c.ORDER -1 end
from dbo.ITEMS c
inner join dbo.ITEMS c2 on c.ORDER = c2.ORDER or c.ORDER = c2.ORDER + 1
where c2.ID=4
However, I was not able to change this query to support hole. I'm trying to do :
UPDATE dbo.ITEMS
set case when c.ORDER = c2.ORDER then min(c2.ORDER ) else c2.ORDER end
FROM dbo.ITEMS c
inner join ITEMS c2 on c2.ORDER >= c.ORDER
where c2.ID=4
group by c.CAT_ID, c.ORDER
having c.ORDER = min(c2.ORDER ) or c.ORDER = c2.ORDER
However, this does not works as expected (the query updates all items having a greater order instead of the two orders to swap).
PS: I'm working with C# 2.0 on Sybase ASE 4.5, but I assume this question is not specific to this platform. If you have a MSSQL, MySql or Oracle equivalent, I'll put effort to convert it ;)
NOTE All below solutions assume that ItemOrder is unique
EDIT Adding a solution that is more like what OP was trying, and may be more portable to Sybase, this time on Microsoft SQL Server 2008. (See below for solutions using Oracle's analytic functions, that may be more efficient if available.)
First the select to get our row selection criteria correct:
declare #MoveUpId int
set #MoveUpId = 4
select current_row.Id
, current_row.ItemOrder
, prior_row.id as PriorRowId
, prior_row.ItemOrder as PriorItemOrder
, next_row.id as NextRowId
, next_row.ItemOrder as NextItemOrder
from #Items current_row
left outer join #Items prior_row
on prior_row.ItemOrder = (select max(ItemOrder)
from #Items
where ItemOrder < current_row.ItemOrder)
left outer join #Items next_row
on next_row.ItemOrder = (select min(ItemOrder)
from #Items
where ItemOrder > current_row.ItemOrder)
where #MoveUpId in (current_row.id, next_row.id)
Then the update based on the above:
update current_row
set ItemOrder = case
when current_row.Id = #MoveUpId then prior_row.ItemOrder
else next_row.ItemOrder end
from #Items current_row
left outer join #Items prior_row
on prior_row.ItemOrder = (select max(ItemOrder)
from #Items
where ItemOrder < current_row.ItemOrder)
left outer join #Items next_row
on next_row.ItemOrder = (select min(ItemOrder)
from #Items
where ItemOrder > current_row.ItemOrder)
where #MoveUpId in (current_row.id, next_row.id)
Id ItemOrder
1 1
2 2
3 20
4 5
5 100
6 4000
10 -1
20 -2
Set #MoveUpId to 20 and rerun above query results in:
Id ItemOrder
1 1
2 2
3 20
4 5
5 100
6 4000
10 -2
20 -1
but I assume this question is not specific to this platform. The question may not be specific, but the answer probably is. For example, using Oracle, first, a table and some test data:
create table Items (Id number(38) not null
, ItemOrder number);
insert into items values (1, 1);
insert into items values (2, 2);
insert into items values (3, 5);
insert into items values (4, 20);
insert into items values (5, 100);
insert into items values (6, 4000);
insert into items values (10, -1);
insert into items values (20, -2);
commit;
Next create a query that returns just the rows we want to update with their new values for Order. (Which I named ItemOrder, Order being a reserved word and all.) In Oracle this is simpliest using the analytic functions lag and lead:
select *
from (select Id
, ItemOrder
, lead(Id) over (order by Id) as LeadId
, lead(ItemOrder) over (order by Id) as LeadItemOrder
, lag(ItemOrder) over (order by Id) as LagItemOrder
from Items)
where 4 in (Id, LeadId)
order by Id;
ID ITEMORDER LEADID LEADITEMORDER LAGITEMORDER
---------- ---------- ---------- ------------- ------------
3 5 4 20 2
4 20 5 100 5
Convert that into an update statement. However the above query will not create an updateable view (in Oracle), so use merge instead:
merge into Items TRGT
using (select Id
, ItemOrder
, lead(Id) over (order by Id) as LeadId
, lead(ItemOrder) over (order by Id) as LeadItemOrder
, lag(ItemOrder) over (order by Id) as LagItemOrder
from Items) SRC
on (SRC.Id = TRGT.Id)
when matched then update
set ItemOrder = case TRGT.Id
when 4 then SRC.LagItemOrder
else SRC.LeadItemOrder end
where 4 in (SRC.Id, SRC.LeadId);
select * from Items order by Id;
ID ITEMORDER
---------- ----------
1 1
2 2
3 20
4 5
5 100
6 4000
10 -1
20 -2
Unfortunately, I do not believe lag and lead are widely implemented. Microsoft SQL Server, as far as I know, has yet to implement them. No experience with ASE, it they have them great.
Row_number() is more widely implemented. Row_number() can be used to get something that is gap free. (Row_number() is refered to as an analytic function on Oracle and a windowed function on SQL Server.) First the query:
with t as (select Id
, ItemOrder
, row_number() over (order by Id) as RN
from Items)
select current_row.id
, current_row.ItemOrder
, next_row.Id as NextId
, next_row.ItemOrder NextItemOrder
, prior_row.ItemOrder PriorItemOrder
from t current_row
left outer join t next_row on next_row.RN = current_row.RN + 1
left outer join t prior_row on prior_row.RN = current_row.RN - 1
where 4 in (current_row.id, next_row.id);
ID ITEMORDER NEXTID NEXTITEMORDER PRIORITEMORDER
---------- ---------- ---------- ------------- --------------
3 5 4 20 2
4 20 5 100 5
Doing the update, again with merge instead of update. (Oracle does allow the update ... from ... join ... syntax, one may be able to get away with update instead of merge on other platforms.)
merge into Items TRGT
using (with t as (select Id
, ItemOrder
, row_number() over (order by Id) as RN
from Items)
select current_row.id
, current_row.ItemOrder
, next_row.Id as NextId
, next_row.ItemOrder as NextItemOrder
, prior_row.ItemOrder as PriorItemOrder
from t current_row
left outer join t next_row on next_row.RN = current_row.RN + 1
left outer join t prior_row on prior_row.RN = current_row.RN - 1
where 4 in (current_row.id, next_row.id)) SRC
on (TRGT.Id = SRC.Id)
when matched then update
set ItemOrder = case
when TRGT.Id = 4 then SRC.PriorItemOrder
else SRC.NextItemOrder end;
select *
from Items
order by Id;
ID ITEMORDER
---------- ----------
1 1
2 2
3 20
4 5
5 100
6 4000
10 -1
20 -2
NOTE Note the solutions above will write null over OrderItems if matching against the Id for the first row.

How to find "holes" in a table

I recently inherited a database on which one of the tables has the primary key composed of encoded values (Part1*1000 + Part2).
I normalized that column, but I cannot change the old values.
So now I have
select ID from table order by ID
ID
100001
100002
101001
...
I want to find the "holes" in the table (more precisely, the first "hole" after 100000) for new rows.
I'm using the following select, but is there a better way to do that?
select /* top 1 */ ID+1 as newID from table
where ID > 100000 and
ID + 1 not in (select ID from table)
order by ID
newID
100003
101029
...
The database is Microsoft SQL Server 2000. I'm ok with using SQL extensions.
select ID +1 From Table t1
where not exists (select * from Table t2 where t1.id +1 = t2.id);
not sure if this version would be faster than the one you mentioned originally.
SELECT (ID+1) FROM table AS t1
LEFT JOIN table as t2
ON t1.ID+1 = t2.ID
WHERE t2.ID IS NULL
This solution should give you the first and last ID values of the "holes" you are seeking. I use this in Firebird 1.5 on a table of 500K records, and although it does take a little while, it gives me what I want.
SELECT l.id + 1 start_id, MIN(fr.id) - 1 stop_id
FROM (table l
LEFT JOIN table r
ON l.id = r.id - 1)
LEFT JOIN table fr
ON l.id < fr.id
WHERE r.id IS NULL AND fr.id IS NOT NULL
GROUP BY l.id, r.id
For example, if your data looks like this:
ID
1001
1002
1005
1006
1007
1009
1011
You would receive this:
start_id stop_id
1003 1004
1008 1008
1010 1010
I wish I could take full credit for this solution, but I found it at Xaprb.
from How do I find a "gap" in running counter with SQL?
select
MIN(ID)
from (
select
100001 ID
union all
select
[YourIdColumn]+1
from
[YourTable]
where
--Filter the rest of your key--
) foo
left join
[YourTable]
on [YourIdColumn]=ID
and --Filter the rest of your key--
where
[YourIdColumn] is null
The best way is building a temp table with all IDs
Than make a left join.
declare #maxId int
select #maxId = max(YOUR_COLUMN_ID) from YOUR_TABLE_HERE
declare #t table (id int)
declare #i int
set #i = 1
while #i <= #maxId
begin
insert into #t values (#i)
set #i = #i +1
end
select t.id
from #t t
left join YOUR_TABLE_HERE x on x.YOUR_COLUMN_ID = t.id
where x.YOUR_COLUMN_ID is null
Have thought about this question recently, and looks like this is the most elegant way to do that:
SELECT TOP(#MaxNumber) ROW_NUMBER() OVER (ORDER BY t1.number)
FROM master..spt_values t1 CROSS JOIN master..spt_values t2
EXCEPT
SELECT Id FROM <your_table>
This solution doesn't give all holes in table, only next free ones + first available max number on table - works if you want to fill in gaps in id-es, + get free id number if you don't have a gap..
select numb + 1 from temp
minus
select numb from temp;
This will give you the complete picture, where 'Bottom' stands for gap start and 'Top' stands for gap end:
select *
from
(
(select <COL>+1 as id, 'Bottom' AS 'Pos' from <TABLENAME> /*where <CONDITION*/>
except
select <COL>, 'Bottom' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/)
union
(select <COL>-1 as id, 'Top' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/
except
select <COL>, 'Top' AS 'Pos' from <TABLENAME> /*where <CONDITION>*/)
) t
order by t.id, t.Pos
Note: First and Last results are WRONG and should not be regarded, but taking them out would make this query a lot more complicated, so this will do for now.
Many of the previous answer are quite good. However they all miss to return the first value of the sequence and/or miss to consider the lower limit 100000. They all returns intermediate holes but not the very first one (100001 if missing).
A full solution to the question is the following one:
select id + 1 as newid from
(select 100000 as id union select id from tbl) t
where (id + 1 not in (select id from tbl)) and
(id >= 100000)
order by id
limit 1;
The number 100000 is to be used if the first number of the sequence is 100001 (as in the original question); otherwise it is to be modified accordingly
"limit 1" is used in order to have just the first available number instead of the full sequence
For people using Oracle, the following can be used:
select a, b from (
select ID + 1 a, max(ID) over (order by ID rows between current row and 1 following) - 1 b from MY_TABLE
) where a <= b order by a desc;
The following SQL code works well with SqLite, but should be used without issues also on MySQL, MS SQL and so on.
On SqLite this takes only 2 seconds on a table with 1 million rows (and about 100 spared missing rows)
WITH holes AS (
SELECT
IIF(c2.id IS NULL,c1.id+1,null) as start,
IIF(c3.id IS NULL,c1.id-1,null) AS stop,
ROW_NUMBER () OVER (
ORDER BY c1.id ASC
) AS rowNum
FROM |mytable| AS c1
LEFT JOIN |mytable| AS c2 ON c1.id+1 = c2.id
LEFT JOIN |mytable| AS c3 ON c1.id-1 = c3.id
WHERE c2.id IS NULL OR c3.id IS NULL
)
SELECT h1.start AS start, h2.stop AS stop FROM holes AS h1
LEFT JOIN holes AS h2 ON h1.rowNum+1 = h2.rowNum
WHERE h1.start IS NOT NULL AND h2.stop IS NOT NULL
UNION ALL
SELECT 1 AS start, h1.stop AS stop FROM holes AS h1
WHERE h1.rowNum = 1 AND h1.stop > 0
ORDER BY h1.start ASC