Query select with next id order - sql

I have a table with ID and NextID like this:
MainID || NextID
1 || 2
2 || 3
3 || 5
4 || 6
5 || 4
6 || ...
... || ...
what I want to achieve is select data into like this
MainID || NextID
1 || 2
2 || 3
3 || 5
5 || 4
4 || 6
6 || ...
... || ...
what i've tried is simple query like :
SELECT * FROM 'table' ORDER BY NextID
but of course it didn't meet my needs,
I have an idea to create a temp table and insert with loop but takes too much time to complete :
WHILE #NextID IS NOT NULL
BEGIN
INSERT INTO 'table'(MainID, NextID)
SELECT MainID, NextId
FROM 'table' WHERE MainID=#NextID
END
Can anyone help me?
Thanks

Recursive cte will return rows in the order of nodes visited
with t as (
select f.*, right('00000000'+cast(f.mainId as varchar(max)),9) path
from yourtable f
where MainID=1
union all
select f.*, path + '->' + right('00000000'+cast(f.mainId as varchar(max)),9)
from t
join yourtable f on t.NextID = f.MainID
)
select *
from t
order by path
db<>fiddle
where MainId=1 is an arbitrary start. You may wish also start with
where not exists (select 1 from yourtable f2 where f2.Nextid = f.MainId)
Edit
Added explicit order by

For this particular case you may use right join with some ordering
select t2.*
from some_table t1
right join some_table t2
on t1. main_id = t2.next_id
order by case when t2.next_id is null then 9999999 else t2.main_id + t2.next_id end;
the 999999 in the "order by" part is to place last line (6, null) to the end of the output.
Good luck with adopting the query to your real data

Related

Concanate data from one table to another

I want to concanate the data from TABLE1 to TABLE 2
TABLE 1
id grp_name
-----------------------------
1 A#erf,R#erf.in
2 B#go.in,D#st.org/S#rec.uy
3 C#st.org,X#we.in,S#erl.in
4 D#gh.ou#F#rt.ot
5 E#rth.or
TABLE 2
code name
-----------------------------------
1 A#we.ot,D#ref.as
2 B#de.in
3 C#gr.cpm
4 D#yahoo.com
5 E#erf.com
6 F#google.com
I want to join grp_name data with name data like concanate using comma (',')
grp_name data having unwanted symboles like '#', '/', I want to elimate those too.
I created below procedure, but i dont know i effective or not.
If it is possible with simple update statement alone or merge statement alone let me know.
Excepted result
code name
1 A#we.ot,D#ref.as,A#erf,R#erf.in
2 B#de.in,B#go.in,D#st.org,S#rec.uy
3 C#gr.cpm,C#st.org,X#we.in,S#erl.in
4 D#yahoo.com,D#gh.ou,F#rt.ot
5 E#erf.com,E#rth.or
6 F#google.com
CREATE OR REPLACE PROCEDURE procedure1
AS
CURSOR cur
IS
SELECT id, grp_name
FROM TABLE 1;
CURSOR cur2
IS
SELECT code, name
FROM TABLE 2;
v_a VARCHAR2(300);
v_b VARCHAR2(25);
v_c VARCHAR2(4000);
v_d VARCHAR2(250);
BEGIN
FOR i IN cur
LOOP
v_a := ','||i.grp_name;
v_b := i.id;
FOR e IN cur2
LOOP
v_c := e.name || v_a ;
v_d := i.code;
UPDATE schema_name.TABLE 2
SET name = v_c
WHERE v_d = v_b;
END LOOP;
END LOOP;
-- COMMIT;
END;
In the simplest case, like #jarlh said, you can just do an update:
update table2
set name = name
|| (select ',' || regexp_replace(grp_name, '[/#]', ',') -- replace / and # with ,
from table1
where table1.id = table2.code)
where code in (select id from table1); -- only update matching rows
The last line is not strictly necessary, but it's good to avoid unnecessary updates.
If you also have rows in TABLE1 which don't have a match in TABLE2 that you want to add, use an insert:
insert into table2 (code, name)
select id, regexp_replace(grp_name, '[/#]', ',')
from table1
where id not in (select code from table2);
Edit: and like Gordon mentioned, you would have an easier time with the SQL if you stored your data like this:
code name
1 A#we.ot
1 D#ref.as
1 A#erf
1 R#erf.in
2 B#de.in
2 B#go.in
2 D#st.org
2 S#rec.uy
3 C#gr.cpm
3 C#st.org
3 X#we.in
3 S#erl.in
Please try something like this:
With tab1 as
(
select 1 as id,'A#er#f,R#erf.in' as grp_name from dual union all
select 2 as id,'B#go.in,D#st.or#g/S#rec.uy' as grp_name from dual
),
tab2 as (
select 1 as id,'A#we.ot,D#ref.as' as name from dual union all
select 2 as id,'B#de.in' as name from dual
)
select REGEXP_REPLACE (grp_name||','||name,'[^a-zA-Z0-9|\#|\,]','') as name from tab1 inner join tab2 using(id);
Result:
A#erf,R#erfin,A#weot,D#refas
B#goin,D#storgS#recuy,B#dein

doing an update statement involving a join in oracle sql

I tried the following code, but it did not work
BEGIN
For i in (select BUS_RPT_ID, BUS_RPT_PRIMARY_POC_ID from BUS_RPT_DTL )
LOOP
update BUS_RPT_DTL
set BUS_RPT_DTL.BUS_RPT_PRIMARY_POC_ID = (select usr_id
from BUS_RPT_DTL b
join FNM_USR u
on LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) =LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME))
where b.BUS_RPT_ID = i.BUS_RPT_ID
and i.BUS_RPT_PRIMARY_POC_ID is not null
);
END LOOP;
END;
i basically have a report table with a poc id and a poc name, the poc name is fillled out but i want to pull the poc id from a usr table and plug it into the poc id in the report table, can anyone help me out?
You dont need a loop. A single update statement would be sufficient.
update BUS_RPT_DTL b
set b.BUS_RPT_PRIMARY_POC_ID = (select usr_id
from FNM_USR u
on LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) =LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME))
)
Where b.BUS_RPT_PRIMARY_POC_ID is not null
Cheers!!
create table BUS_RPT_DTL as
(select 1 bus_rpt_id, 101 bus_rpt_primary_poc_id, 'Joe Dubb' BUS_RPT_PRIMARY_POC_NME from dual union
select 2 bus_rpt_id, 202, 'Bernie Bro' BUS_RPT_PRIMARY_POC_NME from dual union
select 3 bus_rpt_id, null, 'Don Junior' BUS_RPT_PRIMARY_POC_NME from dual
)
;
create table FNM_USR as
( select 909 usr_id, 'Joe' frst_name, 'Dubb' lst_name from dual union
select 808 usr_id, 'Bernie' frst_name, 'Bro' lst_name from dual union
select 707 usr_id, 'Don' frst_name, 'Junior' lst_name from dual
)
;
select * from BUS_RPT_DTL;
update BUS_RPT_DTL b set bus_rpt_primary_poc_id = (select usr_id from fnm_usr u where LOWER(trim(u.FRST_NAME || ' ' || u.LST_NAME)) = LOWER(trim(b.BUS_RPT_PRIMARY_POC_NME)))
where BUS_RPT_PRIMARY_POC_ID is not null
;
select * from BUS_RPT_DTL;
You can alternatively use a Merge Statement, in which you can Update the column BUS_RPT_PRIMARY_POC_ID for the matching cases for your Where clause, otherwise it would Insert new rows.
MERGE INTO BUS_RPT_DTL bb
USING ( SELECT USR_ID
FROM BUS_RPT_DTL b
JOIN FNM_USR u
ON LOWER(TRIM(u.FRST_NAME || ' ' || u.LST_NAME)) =
LOWER(TRIM(b.BUS_RPT_PRIMARY_POC_NME)) b
ON ( bb.BUS_RPT_ID = b.BUS_RPT_ID AND bb.BUS_RPT_PRIMARY_POC_ID IS NOT NULL )
WHEN MATCHED THEN UPDATE SET bb.BUS_RPT_PRIMARY_POC_ID = b.USR_ID
WHEN NOT MATCHED THEN INSERT(bb.BUS_RPT_PRIMARY_POC_NME, bb.BUS_RPT_ID, bb.BUS_RPT_PRIMARY_POC_ID)
VALUES(b.BUS_RPT_PRIMARY_POC_NME , b.BUS_RPT_ID , b.BUS_RPT_PRIMARY_POC_ID );

Select Rows having values matching in 2 other columns

Table
Id || IdFrom || IdTo
--------------------------
1 || null || 2
2 || 1 || null
3 || null || 5
4 || null || 6
5 || 3 || 9
6 || 4 || 7
7 || 6 || null
8 || null || null
9 || 5 || 10
10 || 9 || null
947 || null || 949
949 || 947 || 952
950 || null || 951
951 || 950 || 952
952 || 951 || null
Need to get all rows or just specifically the Ids that are found between all 3 columns when specifying a given Id. So a SELECT (all ids found in the IdFrom or IdTo and Those IdFrom's or IdTo's are in other IdFrom's or IdTo's)
Results when searching for Id 1 would give results of Ids 1 and 2
Results when searching for Id 2 would give results of Ids 1 and 2
Results when searching for Id 3,5,9, or 10 would give results of Ids 3,5,9, and 10
Results when searching for Id 4,6, or 7 would give results of Ids 4,6,and 7
My current search is an iteration getting IdFrom and IdTo for Id, putting those found Id's into a tmp table and iterating back again searching for matches until no more distinct Ids are found. It works but is extremely ugly and takes longer then probably could...
Came across a query that can get all rows that have matching but not specifying for a particular id
DECLARE #SearchForId int = 1
SELECT
t1.ID,t1.IdFROM,t1.IdTO
FROM SomeTable t1
WHERE
(
EXISTS(SELECT Id FROM SomeTable tblFROM WHERE tblFROM.IdFROM = t1.Id) OR
EXISTS(SELECT Id FROM SomeTable tblTO WHERE IdTO.IDTRANSFEREDTO = t1.Id)
)
AND Id = 1 <<-- this part just gives that id obvious but without it, it gets everything in the entire table
EDIT: added new ids (947-952). The previous selected solution did provide ids 947 and 949 but missing 950,951,952. Tried adding another couple cte's like the previous solution to give all ids 947,949,950,951,952 but only giving 947 and 949. How to get all 5. That solution was much quicker by almost by 25x. Would like to keep it and get remainder id's
You need to do it using two recursive common table expressions.
declare #id int
set #id = 2
;WITH CTE1
AS(
SELECT c.*
FROM tbl c
WHERE c.Id = #id
UNION ALL
SELECT p.*
FROM CTE1 cte1_alias
INNER JOIN tbl p
ON p.IdFrom = cte1_alias.Id
),
CTE2
AS(
SELECT c.*
FROM tbl c
WHERE c.Id = #id
UNION ALL
SELECT p.*
FROM CTE2 cte2_alias
INNER JOIN tbl p
ON p.IdTo = cte2_alias.Id
)
SELECT Id FROM CTE1
Union
SELECT Id FROM CTE2
As I understand ORing the columns to be equal to the given value would be enough:
Declare #prm int = 1
Select Id
From SomeTable
Where Id=#prm Or IdFrom=#prm Or IdTo=#prm
or, for a different flavour, using in among columns
Declare #prm int = 1
Select Id
From SomeTable
Where #prm in (Id, IdFrom, IdTo)

Finding missing values from a sequence in a SELECT statement [duplicate]

I have table with a unique auto-incremental primary key. Over time, entries may be deleted from the table, so there are "holes" in this field's values. For example, table data may be as follows:
ID | Value | More fields...
---------------------------------
2 | Cat | ...
3 | Fish | ...
6 | Dog | ...
7 | Aardvark | ...
9 | Owl | ...
10 | Pig | ...
11 | Badger | ...
15 | Mongoose | ...
19 | Ferret | ...
I'm interested in a query that will return the list of missing IDs in the table. For the data above, the expected results are:
ID
----
1
4
5
8
12
13
14
16
17
18
Notes:
It is assumed that the initial first ID was 1
The maximum ID that should be examined is the final one, i.e. it's okay to assume that there were no additional entries after the current last one (see additional data on this point below)
A drawback of the above requirements is that the list will not return IDs that were created after ID 19 and that were deleted. I'm currently solving this case in code, because I hold the max ID created. However, if the query can take as a parameter MaxID, and also return those IDs between the current max and MaxID, that would be a nice "bonus" (but certainly not a must).
I'm currently working with MySQL, but consider moving to SQL Server, so I would like the query to fit both. Also, if you are using anything that can't run on SQLite, please mention it, thanks.
I landed on this page hoping to find a solution for SQLITE as this was the only answer I found when searching for this same question for SQLITE.
The final solution I found was from this article here
Float Middle Blog - SQLITE answer
Hope it helps someone else out :-)
the simple solution being:
SELECT DISTINCT id +1
FROM mytable
WHERE id + 1 NOT IN (SELECT DISTINCT id FROM mytable);
genius.
This question often comes up, and sadly, the most common (and most portable) answer is to create a temporary table to hold the IDs that should be there, and do a left join. The syntax is pretty similar between MySQL and SQL Server. The only real difference is the temporary tables syntax.
In MySQL:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max(id) from tbl
create temporary table IDSeq
(
id int
)
while #id < #maxid
begin
insert into IDSeq values(#id)
set #id = #id + 1
end
select
s.id
from
idseq s
left join tbl t on
s.id = t.id
where t.id is null
drop table IDSeq
In SQL Server:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max(id) from tbl
create table #IDSeq
(
id int
)
while #id < #maxid --whatever you max is
begin
insert into #IDSeq values(#id)
set #id = #id + 1
end
select
s.id
from
#idseq s
left join tbl t on
s.id = t.id
where t.id is null
drop table #IDSeq
Here's the query for SQL Server:
;WITH Missing (missnum, maxid)
AS
(
SELECT 1 AS missnum, (select max(id) from #TT)
UNION ALL
SELECT missnum + 1, maxid FROM Missing
WHERE missnum < maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN #TT tt on tt.id = Missing.missnum
WHERE tt.id is NULL
OPTION (MAXRECURSION 0);
Hope this is helpful.
PostgreSQL-only, inspired by other answers here.
SELECT all_ids AS missing_ids
FROM generate_series((SELECT MIN(id) FROM your_table), (SELECT MAX(id) FROM your_table)) all_ids
EXCEPT
SELECT id FROM your_table
I know it's an old question and already has an accepted answer,
but using a temp table isn't really necessary. Fixed formatting (sorry for double post).
DECLARE #TEST_ID integer, #LAST_ID integer, #ID integer
SET #TEST_ID = 1 -- start compare with this ID
SET #LAST_ID = 100 -- end compare with this ID
WHILE #TEST_ID <= #LAST_ID
BEGIN
SELECT #ID = (SELECT <column> FROM <table> WHERE <column> = #TEST_ID)
IF #ID IS NULL
BEGIN
PRINT 'Missing ID: ' + CAST(#TEST_ID AS VARCHAR(10))
END
SET #TEST_ID = #TEST_ID + 1
END
This is an Oracle only solution. It doesn't address the full question, but is left here for others that may be using Oracle.
select level id -- generate 1 .. 19
from dual
connect by level <= 19
minus -- remove from that set
select id -- everything that is currently in the
from table -- actual table
to get the missing rows from table
DECLARE #MaxID INT = (SELECT MAX(ID) FROM TABLE1)
SELECT SeqID AS MissingSeqID
FROM (SELECT ROW_NUMBER() OVER (ORDER BY column_id) SeqID from sys.columns) LkUp
LEFT JOIN dbo.TABLE1 t ON t.ID = LkUp.SeqID
WHERE t.ID is null and SeqID < #MaxID
I just have found the solution for Postgres:
select min(gs)
from generate_series(1, 1999) as gs
where gs not in (select id from mytable)
The single query can find the missing IDs..
SELECT distinct number
FROM master..spt_values
WHERE number BETWEEN 1 and (SELECT max(id) FROM MyTable)
AND number NOT IN (SELECT id FROM MyTable)
Update: This method took way too long so I wrote a linux command to find gaps in a text file. It does so in reverse order so first dump all id's to a text file like so;
nohup mysql --password=xx -e 'select id from tablename order by id desc' databasename > /home/ids.txt &
The first and last two lines are just to keep track of how long it took. 1.5million IDs(ish) took me 57sec & that's on a slow server. Set the max id in i and have at it.
T="$(date +%s)"; \
i=1574115; \
while read line; do \
if [[ "$line" != "$i" ]] ; then \
if [[ $i -lt 1 ]] ; then break; fi; \
if [[ $line -gt 1 ]] ; then \
missingsequenceend=$(( $line + 1 )); \
minusstr="-"; \
missingsequence="$missingsequenceend$minusstr$i"; \
expectnext=$(( $line - 1 )); \
i=$expectnext; \
echo -e "$missingsequence"; \
fi; \
else \
i=$(( $i - 1 )); \
fi; \
done \
< /home/ids.txt; \
T="$(($(date +%s)-T))"; \
echo "Time in seconds: ${T}"
Example output:
1494505-1494507
47566-47572
Time in seconds: 57
Also, I got syntax errors with the code from Eric's answer, but after changing the delimiter, using semicolons in the proper places and storing it in a procedure, it works.
Make sure you set the proper max ID, database name and table name (it's in the select query). And if you want to change the procedure name, change it in all 3 places.
use dbname;
drop procedure if exists dorepeat;
delimiter #
CREATE PROCEDURE dorepeat()
BEGIN
set #id = 1;
set #maxid = 1573736;
drop table if exists IDSeq;
create temporary table IDSeq
(
id int
);
WHILE #id < #maxid DO
insert into IDSeq values(#id);
set #id = #id + 1;
END WHILE;
select
s.id
from
IDSeq s
left join tablename t on
s.id = t.id
where t.id is null;
drop table if exists IDSeq;
END#
delimiter ;
CALL dorepeat;
I also found this query elwhere, but I haven't tested it.
SELECT a.id+1 AS start, MIN(b.id) - 1 AS end
FROM tablename AS a, tablename AS b
WHERE a.id < b.id
GROUP BY a.id
HAVING start < MIN(b.id)
TRY in MySQL
DELIMITER ||
DROP PROCEDURE IF EXISTS proc_missing ||
CREATE PROCEDURE proc_missing()
BEGIN
SET #minID = (SELECT MIN(`id`) FROM `tbl_name` WHERE `user_id`=13);
SET #maxID = (SELECT MAX(`id`) FROM `tbl_name` WHERE `user_id`=13);
REPEAT
SET #tableID = (SELECT `id` FROM `tbl_name` WHERE `id` = #minID);
IF (#tableID IS NULL) THEN
INSERT INTO temp_missing SET `missing_id` = #tableID;
END IF;
SET #minID = #minID + 1;
UNTIL(#minID <= #maxID)
END REPEAT;
END ||
DELIMITER ;
A few days ago, I was working on a production report and found some numbers missing. The missing numbers are very important, so I was asked to find a list of all missing numbers for investigation purposes. I posted a blog entry here, with a full demo, including a script to find missing numbers/IDs in a sample table.
The script suggested is quite long, so I won't include it here. Here are the basic steps used:
Create one temp table and store all distinct Numbers.
Find NextID which has something missing before it. Store into one TempTable.
Create one temp table to store missing number details.
Start to find the missing id using WHILE Loop.
Select missing data from #MissingID temp table.
Converting the SQL CTE (from Paul Svirin) to the Oracle version it looks like this (replace :YOURTABLE with the name of your table):
WITH Missing (missnum,maxid) as (
SELECT 1 missnum, (select max(id) from :YOURTABLE) maxid from dual
UNION ALL
SELECT m.missnum + 1,m.maxid
FROM Missing m
WHERE m.missnum < m.maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN :YOURTABLE tt on tt.id = Missing.missnum
WHERE tt.id is NULL
Using #PaulSvirin's answer, I've expanded it with a UNION to show ALL the data in my table, including the missing records with NULLs.
WITH Missing(missnum, maxid) AS
(SELECT (SELECT MIN(tmMIN.TETmeetingID)
FROM tblTETMeeting AS tmMIN)
AS missnum,
(SELECT MAX(tmMAX.TETmeetingID)
FROM tblTETMeeting AS tmMAX)
AS maxid
UNION ALL
SELECT missnum + 1, maxid
FROM Missing
WHERE missnum < maxid)
SELECT missnum AS TETmeetingID,
tt.DateID,
tt.WeekNo,
tt.TETID
FROM Missing LEFT JOIN tblTETMeeting tt ON tt.TETmeetingID = Missing.missnum
WHERE tt.TETmeetingID IS NULL
UNION
SELECT tt.TETmeetingID,
tt.DateID,
tt.WeekNo,
tt.TETID
FROM tblTETMeeting AS tt
OPTION ( MAXRECURSION 0 )
Work's great!
TETmeetingID DateID WeekNo TETID
29 3063 21 1
30 null null null
31 null null null
32 null null null
33 null null null
34 3070 22 1
35 3073 23 1
Easiest solution for me: Create a select that gives all ids up to max sequence value (ex:1000000), and filter:
with listids as (
Select Rownum idnumber From dual Connect By Rownum <= 1000000)
select * from listids
where idnumber not in (select id from table where id <=1000000)
A modified version borrowing #Eric proposal. This is for SQL Server and holds in a temp table the start and end value for missing ranges. If the gap is just one value it puts NULL as end value for easier visualization.
It will produce an output like this
|StartId| EndId |
|-------|-------|
| 1 | 10182 |
| 10189 | NULL |
| 10246 | 15000 |
And this is the script where myTable and id needs to be replaced by your table and identity column.
declare #id bigint
declare #endId bigint
declare #maxid bigint
declare #previousid bigint=0
set #id = 1
select #maxid = max(id) from myTable
create table #IDGaps
(
startId bigint,
endId bigint
)
while #id < #maxid
begin
if NOT EXISTS(select id from myTable where id=#id)
BEGIN
SET #previousid=#id
select top 1 #endId=id from myTable where id>#id
IF #id=#endId-1
insert into #IDGaps values(#id,null)
ELSE
insert into #IDGaps values(#id,#endId-1)
SET #id=#endId
END
ELSE
set #id = #id + 1
end
select * from #IDGaps
drop table #IDGaps
SOLUTION FOR SQLITE
if your table id only support positive values you can use this
SELECT DISTINCT table_id - 1 AS next_id
FROM table
WHERE next_id NOT IN (SELECT DISTINCT table_id FROM table)
AND next_id > 0
otherwise you should remove ids greater than the biggest id with
SELECT DISTINCT table_id + 1 AS next_id
FROM table
WHERE next_id NOT IN (SELECT DISTINCT table_id FROM table)
AND id < (SELECT MAX(id) FROM table)
This problem can be solved with only one query
select lft.id + 1 as missing_ids
from tbl as lft left outer join tbl as rght on lft.id + 1 = rght.id
where rght.id is null and lft.id between 1 and (Select max(id)-1 from tbl)
Tested on Mysql
Try This Query. This single query is enough to get missing numbers:(Please replace TABLE_NAME to which table name you are using)
select sno as missing from(SELECT #row := #row + 1 as sno FROM
(select 0 union all select 1 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 6 union all select 7 union all
select 8 union all select 9) t,(select 0 union all select 1 union all select 3
union all select 4 union all select 5 union all select 6 union all select 6
union all select 7 union all select 8 union all select 9) t2,(select 0
union all select 1 union all select 3 union all select 4 union all select 5
union all select 6 union all select 6 union all select 7 union all select 8
union all select 9) t3, (select 0 union all select 1 union all select 3 union
all select 4 union all select 5 union all select 6 union all select 6 union all
select 7 union all select 8 union all select 9) t4,
(SELECT #row:=0) as b where #row<1000) as a where a.sno not in
(select distinct b.no from
(select b.*,if(#mn=0,#mn:=b.no,#mn) as min,(#mx:=b.no) as max from
(select ID as no from TABLE_NAME as a) as b,
(select #mn:=0,#mx:=0) as x order by no) as b) and
a.sno between #mn and #mx;
SELECT DISTINCT id -1
FROM users
WHERE id != 1 AND id - 1 NOT IN (SELECT DISTINCT id FROM users)
Explanation: ( id - 1 )..... checking for any previous id present in table
( id != 1 ).....neglecting when current id is 1 as its previous id will be 0 zero.
I have a large audit table and needed something that ran quickly - this worked well for me. It merges the top and bottom IDs for the missing ranges
select minQ.num,minId,maxId from
(SELECT DISTINCT id +1 as minId, Row_Number() Over ( Order By id ) As Num
FROM tblAuditLoghistory
WHERE id + 1 NOT IN (SELECT DISTINCT id FROM tblAuditLogHistory)
AND id < (SELECT max(id) FROM tblAuditLoghistory)) Minq
join
(SELECT DISTINCT id - 1 as maxId, Row_Number() Over ( Order By id ) As Num
FROM tblAuditLoghistory
WHERE id - 1 NOT IN (SELECT DISTINCT id FROM tblAuditLogHistory)
AND id > (SELECT min(id) FROM tblAuditLoghistory)) maxQ on minQ.num=maxQ.num
This what i used to find the missing id of one table named as tablename
select a.id+1 missing_ID from tablename a
where a.id+1 not in (select id from tablename b where b.id=a.id+1)
and a.id!=(select id from tablename c order by id desc limit 1)
It will return the missing ids.
If there are two(2) or more continuous missing ids, it will return only the first.

SQL: find missing IDs in a table

I have table with a unique auto-incremental primary key. Over time, entries may be deleted from the table, so there are "holes" in this field's values. For example, table data may be as follows:
ID | Value | More fields...
---------------------------------
2 | Cat | ...
3 | Fish | ...
6 | Dog | ...
7 | Aardvark | ...
9 | Owl | ...
10 | Pig | ...
11 | Badger | ...
15 | Mongoose | ...
19 | Ferret | ...
I'm interested in a query that will return the list of missing IDs in the table. For the data above, the expected results are:
ID
----
1
4
5
8
12
13
14
16
17
18
Notes:
It is assumed that the initial first ID was 1
The maximum ID that should be examined is the final one, i.e. it's okay to assume that there were no additional entries after the current last one (see additional data on this point below)
A drawback of the above requirements is that the list will not return IDs that were created after ID 19 and that were deleted. I'm currently solving this case in code, because I hold the max ID created. However, if the query can take as a parameter MaxID, and also return those IDs between the current max and MaxID, that would be a nice "bonus" (but certainly not a must).
I'm currently working with MySQL, but consider moving to SQL Server, so I would like the query to fit both. Also, if you are using anything that can't run on SQLite, please mention it, thanks.
I landed on this page hoping to find a solution for SQLITE as this was the only answer I found when searching for this same question for SQLITE.
The final solution I found was from this article here
Float Middle Blog - SQLITE answer
Hope it helps someone else out :-)
the simple solution being:
SELECT DISTINCT id +1
FROM mytable
WHERE id + 1 NOT IN (SELECT DISTINCT id FROM mytable);
genius.
This question often comes up, and sadly, the most common (and most portable) answer is to create a temporary table to hold the IDs that should be there, and do a left join. The syntax is pretty similar between MySQL and SQL Server. The only real difference is the temporary tables syntax.
In MySQL:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max(id) from tbl
create temporary table IDSeq
(
id int
)
while #id < #maxid
begin
insert into IDSeq values(#id)
set #id = #id + 1
end
select
s.id
from
idseq s
left join tbl t on
s.id = t.id
where t.id is null
drop table IDSeq
In SQL Server:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max(id) from tbl
create table #IDSeq
(
id int
)
while #id < #maxid --whatever you max is
begin
insert into #IDSeq values(#id)
set #id = #id + 1
end
select
s.id
from
#idseq s
left join tbl t on
s.id = t.id
where t.id is null
drop table #IDSeq
Here's the query for SQL Server:
;WITH Missing (missnum, maxid)
AS
(
SELECT 1 AS missnum, (select max(id) from #TT)
UNION ALL
SELECT missnum + 1, maxid FROM Missing
WHERE missnum < maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN #TT tt on tt.id = Missing.missnum
WHERE tt.id is NULL
OPTION (MAXRECURSION 0);
Hope this is helpful.
PostgreSQL-only, inspired by other answers here.
SELECT all_ids AS missing_ids
FROM generate_series((SELECT MIN(id) FROM your_table), (SELECT MAX(id) FROM your_table)) all_ids
EXCEPT
SELECT id FROM your_table
I know it's an old question and already has an accepted answer,
but using a temp table isn't really necessary. Fixed formatting (sorry for double post).
DECLARE #TEST_ID integer, #LAST_ID integer, #ID integer
SET #TEST_ID = 1 -- start compare with this ID
SET #LAST_ID = 100 -- end compare with this ID
WHILE #TEST_ID <= #LAST_ID
BEGIN
SELECT #ID = (SELECT <column> FROM <table> WHERE <column> = #TEST_ID)
IF #ID IS NULL
BEGIN
PRINT 'Missing ID: ' + CAST(#TEST_ID AS VARCHAR(10))
END
SET #TEST_ID = #TEST_ID + 1
END
This is an Oracle only solution. It doesn't address the full question, but is left here for others that may be using Oracle.
select level id -- generate 1 .. 19
from dual
connect by level <= 19
minus -- remove from that set
select id -- everything that is currently in the
from table -- actual table
to get the missing rows from table
DECLARE #MaxID INT = (SELECT MAX(ID) FROM TABLE1)
SELECT SeqID AS MissingSeqID
FROM (SELECT ROW_NUMBER() OVER (ORDER BY column_id) SeqID from sys.columns) LkUp
LEFT JOIN dbo.TABLE1 t ON t.ID = LkUp.SeqID
WHERE t.ID is null and SeqID < #MaxID
I just have found the solution for Postgres:
select min(gs)
from generate_series(1, 1999) as gs
where gs not in (select id from mytable)
The single query can find the missing IDs..
SELECT distinct number
FROM master..spt_values
WHERE number BETWEEN 1 and (SELECT max(id) FROM MyTable)
AND number NOT IN (SELECT id FROM MyTable)
Update: This method took way too long so I wrote a linux command to find gaps in a text file. It does so in reverse order so first dump all id's to a text file like so;
nohup mysql --password=xx -e 'select id from tablename order by id desc' databasename > /home/ids.txt &
The first and last two lines are just to keep track of how long it took. 1.5million IDs(ish) took me 57sec & that's on a slow server. Set the max id in i and have at it.
T="$(date +%s)"; \
i=1574115; \
while read line; do \
if [[ "$line" != "$i" ]] ; then \
if [[ $i -lt 1 ]] ; then break; fi; \
if [[ $line -gt 1 ]] ; then \
missingsequenceend=$(( $line + 1 )); \
minusstr="-"; \
missingsequence="$missingsequenceend$minusstr$i"; \
expectnext=$(( $line - 1 )); \
i=$expectnext; \
echo -e "$missingsequence"; \
fi; \
else \
i=$(( $i - 1 )); \
fi; \
done \
< /home/ids.txt; \
T="$(($(date +%s)-T))"; \
echo "Time in seconds: ${T}"
Example output:
1494505-1494507
47566-47572
Time in seconds: 57
Also, I got syntax errors with the code from Eric's answer, but after changing the delimiter, using semicolons in the proper places and storing it in a procedure, it works.
Make sure you set the proper max ID, database name and table name (it's in the select query). And if you want to change the procedure name, change it in all 3 places.
use dbname;
drop procedure if exists dorepeat;
delimiter #
CREATE PROCEDURE dorepeat()
BEGIN
set #id = 1;
set #maxid = 1573736;
drop table if exists IDSeq;
create temporary table IDSeq
(
id int
);
WHILE #id < #maxid DO
insert into IDSeq values(#id);
set #id = #id + 1;
END WHILE;
select
s.id
from
IDSeq s
left join tablename t on
s.id = t.id
where t.id is null;
drop table if exists IDSeq;
END#
delimiter ;
CALL dorepeat;
I also found this query elwhere, but I haven't tested it.
SELECT a.id+1 AS start, MIN(b.id) - 1 AS end
FROM tablename AS a, tablename AS b
WHERE a.id < b.id
GROUP BY a.id
HAVING start < MIN(b.id)
TRY in MySQL
DELIMITER ||
DROP PROCEDURE IF EXISTS proc_missing ||
CREATE PROCEDURE proc_missing()
BEGIN
SET #minID = (SELECT MIN(`id`) FROM `tbl_name` WHERE `user_id`=13);
SET #maxID = (SELECT MAX(`id`) FROM `tbl_name` WHERE `user_id`=13);
REPEAT
SET #tableID = (SELECT `id` FROM `tbl_name` WHERE `id` = #minID);
IF (#tableID IS NULL) THEN
INSERT INTO temp_missing SET `missing_id` = #tableID;
END IF;
SET #minID = #minID + 1;
UNTIL(#minID <= #maxID)
END REPEAT;
END ||
DELIMITER ;
A few days ago, I was working on a production report and found some numbers missing. The missing numbers are very important, so I was asked to find a list of all missing numbers for investigation purposes. I posted a blog entry here, with a full demo, including a script to find missing numbers/IDs in a sample table.
The script suggested is quite long, so I won't include it here. Here are the basic steps used:
Create one temp table and store all distinct Numbers.
Find NextID which has something missing before it. Store into one TempTable.
Create one temp table to store missing number details.
Start to find the missing id using WHILE Loop.
Select missing data from #MissingID temp table.
Converting the SQL CTE (from Paul Svirin) to the Oracle version it looks like this (replace :YOURTABLE with the name of your table):
WITH Missing (missnum,maxid) as (
SELECT 1 missnum, (select max(id) from :YOURTABLE) maxid from dual
UNION ALL
SELECT m.missnum + 1,m.maxid
FROM Missing m
WHERE m.missnum < m.maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN :YOURTABLE tt on tt.id = Missing.missnum
WHERE tt.id is NULL
Using #PaulSvirin's answer, I've expanded it with a UNION to show ALL the data in my table, including the missing records with NULLs.
WITH Missing(missnum, maxid) AS
(SELECT (SELECT MIN(tmMIN.TETmeetingID)
FROM tblTETMeeting AS tmMIN)
AS missnum,
(SELECT MAX(tmMAX.TETmeetingID)
FROM tblTETMeeting AS tmMAX)
AS maxid
UNION ALL
SELECT missnum + 1, maxid
FROM Missing
WHERE missnum < maxid)
SELECT missnum AS TETmeetingID,
tt.DateID,
tt.WeekNo,
tt.TETID
FROM Missing LEFT JOIN tblTETMeeting tt ON tt.TETmeetingID = Missing.missnum
WHERE tt.TETmeetingID IS NULL
UNION
SELECT tt.TETmeetingID,
tt.DateID,
tt.WeekNo,
tt.TETID
FROM tblTETMeeting AS tt
OPTION ( MAXRECURSION 0 )
Work's great!
TETmeetingID DateID WeekNo TETID
29 3063 21 1
30 null null null
31 null null null
32 null null null
33 null null null
34 3070 22 1
35 3073 23 1
Easiest solution for me: Create a select that gives all ids up to max sequence value (ex:1000000), and filter:
with listids as (
Select Rownum idnumber From dual Connect By Rownum <= 1000000)
select * from listids
where idnumber not in (select id from table where id <=1000000)
A modified version borrowing #Eric proposal. This is for SQL Server and holds in a temp table the start and end value for missing ranges. If the gap is just one value it puts NULL as end value for easier visualization.
It will produce an output like this
|StartId| EndId |
|-------|-------|
| 1 | 10182 |
| 10189 | NULL |
| 10246 | 15000 |
And this is the script where myTable and id needs to be replaced by your table and identity column.
declare #id bigint
declare #endId bigint
declare #maxid bigint
declare #previousid bigint=0
set #id = 1
select #maxid = max(id) from myTable
create table #IDGaps
(
startId bigint,
endId bigint
)
while #id < #maxid
begin
if NOT EXISTS(select id from myTable where id=#id)
BEGIN
SET #previousid=#id
select top 1 #endId=id from myTable where id>#id
IF #id=#endId-1
insert into #IDGaps values(#id,null)
ELSE
insert into #IDGaps values(#id,#endId-1)
SET #id=#endId
END
ELSE
set #id = #id + 1
end
select * from #IDGaps
drop table #IDGaps
SOLUTION FOR SQLITE
if your table id only support positive values you can use this
SELECT DISTINCT table_id - 1 AS next_id
FROM table
WHERE next_id NOT IN (SELECT DISTINCT table_id FROM table)
AND next_id > 0
otherwise you should remove ids greater than the biggest id with
SELECT DISTINCT table_id + 1 AS next_id
FROM table
WHERE next_id NOT IN (SELECT DISTINCT table_id FROM table)
AND id < (SELECT MAX(id) FROM table)
This problem can be solved with only one query
select lft.id + 1 as missing_ids
from tbl as lft left outer join tbl as rght on lft.id + 1 = rght.id
where rght.id is null and lft.id between 1 and (Select max(id)-1 from tbl)
Tested on Mysql
Try This Query. This single query is enough to get missing numbers:(Please replace TABLE_NAME to which table name you are using)
select sno as missing from(SELECT #row := #row + 1 as sno FROM
(select 0 union all select 1 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 6 union all select 7 union all
select 8 union all select 9) t,(select 0 union all select 1 union all select 3
union all select 4 union all select 5 union all select 6 union all select 6
union all select 7 union all select 8 union all select 9) t2,(select 0
union all select 1 union all select 3 union all select 4 union all select 5
union all select 6 union all select 6 union all select 7 union all select 8
union all select 9) t3, (select 0 union all select 1 union all select 3 union
all select 4 union all select 5 union all select 6 union all select 6 union all
select 7 union all select 8 union all select 9) t4,
(SELECT #row:=0) as b where #row<1000) as a where a.sno not in
(select distinct b.no from
(select b.*,if(#mn=0,#mn:=b.no,#mn) as min,(#mx:=b.no) as max from
(select ID as no from TABLE_NAME as a) as b,
(select #mn:=0,#mx:=0) as x order by no) as b) and
a.sno between #mn and #mx;
SELECT DISTINCT id -1
FROM users
WHERE id != 1 AND id - 1 NOT IN (SELECT DISTINCT id FROM users)
Explanation: ( id - 1 )..... checking for any previous id present in table
( id != 1 ).....neglecting when current id is 1 as its previous id will be 0 zero.
I have a large audit table and needed something that ran quickly - this worked well for me. It merges the top and bottom IDs for the missing ranges
select minQ.num,minId,maxId from
(SELECT DISTINCT id +1 as minId, Row_Number() Over ( Order By id ) As Num
FROM tblAuditLoghistory
WHERE id + 1 NOT IN (SELECT DISTINCT id FROM tblAuditLogHistory)
AND id < (SELECT max(id) FROM tblAuditLoghistory)) Minq
join
(SELECT DISTINCT id - 1 as maxId, Row_Number() Over ( Order By id ) As Num
FROM tblAuditLoghistory
WHERE id - 1 NOT IN (SELECT DISTINCT id FROM tblAuditLogHistory)
AND id > (SELECT min(id) FROM tblAuditLoghistory)) maxQ on minQ.num=maxQ.num
This what i used to find the missing id of one table named as tablename
select a.id+1 missing_ID from tablename a
where a.id+1 not in (select id from tablename b where b.id=a.id+1)
and a.id!=(select id from tablename c order by id desc limit 1)
It will return the missing ids.
If there are two(2) or more continuous missing ids, it will return only the first.