Problem in string concatenation in sql server using FOR XML Path. - sql

I have the below data
UniqueID ID data
1 1 a
2 1 2
3 1 b
4 1 1
5 2 d
6 2 3
7 2 r
The expected output being
ID ConcatData
1 a,-,-,b,-
2 d,-,-,-,r
What we have to do is that, the number of numeric charecters has to be replaced with those many dashes('-') and then we need to merge the data for the respective id's.
I am using the below query so far
declare #t table(UniqueID int identity(1,1), ID int, data varchar(10))
insert into #t select 1, 'a' union all select 1, '2' union all select 1, 'b'
union all select 1, '1' union all select 2, 'd' union all select 2, '3'
union all select 2, 'r'
select * from #t
;with cte1 as
(
select
UniqueId
, id
, data
, case when isnumeric(data) = 1 then cast(data as int) end Level
from #t
union all
select
UniqueId
, id
, CAST('-' as varchar(10))
, Level - 1
from cte1
where Level > 0 )
,cte2 as
(
select id, GroupID = Dense_Rank() Over(Order by id),data, DataOrder = ROW_NUMBER() over(order by UniqueID, Level)
from cte1
where Level is null or data = '-'
)
SELECT
ID
, (select data + ','
from cte2 t2
where t2.GroupID = t1.GroupID
for XML path('')
) as ConcatData
from cte2 t1
group by t1.ID ,t1.GroupID
But the output is
ID ConcatData
1 a,b,-,-,-,
2 d,r,-,-,-,
That is I am not able to position the dashes('-') in between the characters.
Please help

Try this:
;with cte1 as
(
select
UniqueId
, id
, data
,case when isnumeric(data) = 1
THEN replicate(',-',data)
ELSE ',' + data end as string
from #t
)
select
id
,LTRIM(STUFF(
(
SELECT
' ' + t2.String
FROM Cte1 t2
WHERE t2.id = t1.id
FOR XML PATH('')
), 2, 1, ''
)) As concatenated_string
from cte1 t1 group by t1.ID ,t1.ID
Works for the sample data bove and might be a bit quicker than using cursors

Following is the table creating
Create table #temp
(
IDUnique int Identity(1,1),
ID int,
data varchar(100)
)
Following are the records suggested by you.
Insert into #temp(ID, data) Values(1, 'a')
Insert into #temp(ID, data) Values(1, '2')
Insert into #temp(ID, data) Values(1, 'b')
Insert into #temp(ID, data) Values(1, '1')
Insert into #temp(ID, data) Values(2, 'd')
Insert into #temp(ID, data) Values(2, '3')
Insert into #temp(ID, data) Values(2, 'r')
Following is the cursor implementation
declare #IDUnique int
declare #ID int
declare #data varchar(100)
declare #Latest int
declare #Previous int
declare #Row int
set #Latest = 1
set #Previous = 1
Create Table #temp1
(
ID int,
data varchar(100)
)
--SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID, data From #temp
DECLARE #getAccountID CURSOR SET #getAccountID = CURSOR FOR SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID, data From #temp
OPEN #getAccountID
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID, #data
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#Row = 1)
Begin
Set #Previous = #ID
Set #Latest = #ID
Insert into #temp1(ID, data)values(#Previous, #data)
End
Else If (#Previous <> #ID)
Begin
Set #Previous = #ID
Set #Latest = #ID
Insert into #temp1(ID, data)values(#Previous, #data)
End
Else
Begin
Declare #number int
if(ISNUMERIC(#data) = 1)
Begin
Set #number = Convert(int , #data)
While(#number <> 0)
Begin
Update #temp1 Set Data = Data + ',-' Where ID = #ID
Set #number = #number - 1
End
End
Else
begin
Update #temp1 Set Data = Data + ',' + #data Where ID = #ID
End
End
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID, #data
END
CLOSE #getAccountID
DEALLOCATE #getAccountID
Select * from #temp1
Select * from #temp
Drop Table #temp
here is the Final Result set

Related

How to traverse a path in a table with id & parentId?

Suppose I have a table like:
id | parentId | name
1 NULL A
2 1 B
3 2 C
4 1 E
5 3 E
I am trying to write a scalar function I can call as:
SELECT dbo.GetId('A/B/C/E') which would produce "5" if we use the above reference table. The function would do the following steps:
Find the ID of 'A' which is 1
Find the ID of 'B' whose parent is 'A' (id:1) which would be id:2
Find the ID of 'C' whose parent is 'B' (id:2) which would be id:3
Find the ID of 'E' whose parent is 'C' (id:3) which would be id:5
I was trying to do it with a WHILE loop but it was getting very complicated very fast... Just thinking there must be a simple way to do this.
CTE version is not optimized way to get the hierarchical data. (Refer MSDN Blog)
You should do something like as mentioned below. It's tested for 10 millions of records and is 300 times faster than CTE version :)
Declare #table table(Id int, ParentId int, Name varchar(10))
insert into #table values(1,NULL,'A')
insert into #table values(2,1,'B')
insert into #table values(3,2,'C')
insert into #table values(4,1,'E')
insert into #table values(5,3,'E')
DECLARE #Counter tinyint = 0;
IF OBJECT_ID('TEMPDB..#ITEM') IS NOT NULL
DROP TABLE #ITEM
CREATE TABLE #ITEM
(
ID int not null
,ParentID int
,Name VARCHAR(MAX)
,lvl int not null
,RootID int not null
)
INSERT INTO #ITEM
(ID,lvl,ParentID,Name,RootID)
SELECT Id
,0 AS LVL
,ParentId
,Name
,Id AS RootID
FROM
#table
WHERE
ISNULL(ParentId,-1) = -1
WHILE ##ROWCOUNT > 0
BEGIN
SET #Counter += 1
insert into #ITEM(ID,ParentId,Name,lvl,RootID)
SELECT ci.ID
,ci.ParentId
,ci.Name
,#Counter as cntr
,ch.RootID
FROM
#table AS ci
INNER JOIN
#ITEM AS pr
ON
CI.ParentId=PR.ID
LEFT OUTER JOIN
#ITEM AS ch
ON ch.ID=pr.ID
WHERE
ISNULL(ci.ParentId, -1) > 0
AND PR.lvl = #Counter - 1
END
select * from #ITEM
Here is an example of functional rcte based on your sample data and requirements as I understand them.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
id int
, parentId int
, name char(1)
)
insert #Something
select 1, NULL, 'A' union all
select 2, 1, 'B' union all
select 3, 2, 'C' union all
select 4, 1, 'E' union all
select 5, 3, 'E'
declare #Root char(1) = 'A';
with MyData as
(
select *
from #Something
where name = #Root
union all
select s.*
from #Something s
join MyData d on d.id = s.parentId
)
select *
from MyData
Note that if you change the value of your variable the output will adjust. I would make this an inline table valued function.
I think I have it based on #SeanLange's recommendation to use a recursive CTE (above in the comments):
CREATE FUNCTION GetID
(
#path VARCHAR(MAX)
)
/* TEST:
SELECT dbo.GetID('A/B/C/E')
*/
RETURNS INT
AS
BEGIN
DECLARE #ID INT;
WITH cte AS (
SELECT p.id ,
p.parentId ,
CAST(p.name AS VARCHAR(MAX)) AS name
FROM tblT p
WHERE parentId IS NULL
UNION ALL
SELECT p.id ,
p.parentId ,
CAST(pcte.name + '/' + p.name AS VARCHAR(MAX)) AS name
FROM dbo.tblT p
INNER JOIN cte pcte ON
pcte.id = p.parentId
)
SELECT #ID = id
FROM cte
WHERE name = #path
RETURN #ID
END

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

How can I "Delete All" but only when a column has a certain value?

I need to MERGE a set of records into a table. The UpdateType column in the source determines if I should DELETE rows in the target that are not in the source.
So UpdateType will equal D or R... D=Delta, R=Refresh
If D, do NOT DELETE non matching from target
If R, DO DELETE non matching from target.
I have a WHILE that iterates over a single table, to just better mimic how the process works.
Can I accomplish this in a MERGE? Or what other options do I have?
SQL Fiddle: http://www.sqlfiddle.com/#!3/9cdfe/16
Here is my example, the only problem is... I can't use a source value in the WHEN NOT MATCHED BY SOURCE clause.
DECLARE #BaseTable TABLE
( RN int
,Store int
,UpdateType char(1)
,ItemNumber int
,Name varchar(50)
)
INSERT INTO #BaseTable
SELECT *
FROM
(
SELECT 1 RN, 1 Store, 'D' UpdateType, 1 ItemNumber, 'Wheel' Name
UNION ALL
SELECT 2, 1, 'D', 1, 'Big Wheel'
UNION ALL
SELECT 3, 1, 'D', 2, 'Light'
UNION ALL
SELECT 4, 1, 'R', 1, 'Wide Wheel'
UNION ALL
SELECT 5, 1, 'D', 1, 'Small Wheel'
UNION ALL
SELECT 5, 1, 'D', 4, 'Trunk'
)B
SELECT bt.* FROM #BaseTable bt
DECLARE #Tab TABLE
( Store int
,UpdateType char(1)
,ItemNumber int
,Name varchar(50)
)
DECLARE #count int = 1
--Loop over each row to mimic how the merge will be called.
WHILE #count <= 5
BEGIN
MERGE INTO #Tab T
USING
(
SELECT bt.RN,
bt.Store,
bt.UpdateType,
bt.ItemNumber,
bt.Name,
tab.Store IsRefresh
FROM #BaseTable bt
LEFT JOIN
( --If ANY previous ITERATION was a 'R' then, all subsequent UpdateType MUST = 'R'
--I'm hoping there is a better way to accomplish this.
SELECT Store
FROM #Tab
WHERE UpdateType = 'R'
GROUP BY Store
HAVING COUNT(Store) > 1
)tab
ON bt.Store = tab.Store
WHERE bt.RN = #count
)S
ON S.Store = T.Store AND S.ItemNumber = T.ItemNumber
WHEN MATCHED THEN
UPDATE
SET T.UpdateType = CASE WHEN S.IsRefresh IS NOT NULL THEN 'R' ELSE S.UpdateType END,
T.Name = S.Name
WHEN NOT MATCHED BY TARGET THEN
INSERT(Store,UpdateType,ItemNumber,Name) VALUES(S.Store,S.UpdateType,S.ItemNumber,S.Name)
--WHEN NOT MATCHED BY SOURCE AND S.UpdateType = 'R' THEN
-- DELETE
;
SET #count = #count + 1
END
SELECT * FROM #Tab
--#Tab Expected Result:
-- 1 'R' 1 'Small Wheel'
-- 1 'R' 4 'Trunk'
The following code appears to do what you want:
CREATE TABLE #BaseTable
( RN int
,Store int
,UpdateType char(1)
,ItemNumber int
,Name varchar(50)
)
INSERT INTO #BaseTable
SELECT *
FROM
(
SELECT 1 RN, 1 Store, 'D' UpdateType, 1 ItemNumber, 'Wheel' Name
UNION ALL
SELECT 2, 1, 'D', 1, 'Big Wheel'
UNION ALL
SELECT 3, 1, 'D', 2, 'Light'
UNION ALL
SELECT 4, 1, 'R', 1, 'Wide Wheel'
UNION ALL
SELECT 5, 1, 'D', 1, 'Small Wheel'
UNION ALL
SELECT 5, 1, 'D', 4, 'Trunk'
)B
CREATE TABLE #Tab
( Store int
,UpdateType char(1)
,ItemNumber int
,Name varchar(50)
)
SELECT bt.* FROM #BaseTable bt -- Output for debugging - delete in production.
DECLARE #count int = 1
DECLARE #Input TABLE (Store int, UpdateType char(1), ItemNumber int, Name varchar(50))
--Loop over each row to mimick how the merge will be called.
WHILE #count <= 5
BEGIN
DELETE FROM #Input
INSERT INTO #Input SELECT Store, UpdateType, ItemNumber, Name FROM #BaseTable WHERE RN = #Count
SELECT * FROM #Input -- Output for debugging - delete in production.
-- Procedure Body
DECLARE #Store int, #UpdateType char(1), #ItemNumber int, #Name varchar(50)
DECLARE csrInput CURSOR FOR SELECT Store, UpdateType, ItemNumber, Name FROM #Input
OPEN csrInput
WHILE 1=1
BEGIN
FETCH NEXT FROM csrInput INTO #Store, #UpdateType, #ItemNumber, #Name
IF ##FETCH_STATUS<>0 BREAK
IF #UpdateType = 'D'
MERGE INTO #Tab Dest
USING (SELECT * FROM #Input WHERE Store = #Store AND ItemNumber = #ItemNumber) Src
ON Dest.Store = Src.Store AND Dest.ItemNumber = Src.ItemNumber
WHEN MATCHED THEN UPDATE SET Dest.UpdateType = Src.UpdateType, Dest.Name = Src.Name
WHEN NOT MATCHED BY TARGET THEN INSERT (Store, UpdateType, ItemNumber, Name) VALUES (Src.Store, Src.UpdateType, Src.ItemNumber, Src.Name);
ELSE -- Assuming that #UpdateType can only be 'D' or 'R'...
MERGE INTO #Tab Dest
USING (SELECT * FROM #Input WHERE Store = #Store AND ItemNumber = #ItemNumber) Src
ON Dest.Store = Src.Store AND Dest.ItemNumber = Src.ItemNumber
WHEN MATCHED THEN UPDATE SET Dest.UpdateType = Src.UpdateType, Dest.Name = Src.Name
WHEN NOT MATCHED BY TARGET THEN INSERT (Store, UpdateType, ItemNumber, Name) VALUES (Src.Store, Src.UpdateType, Src.ItemNumber, Src.Name)
WHEN NOT MATCHED BY SOURCE THEN DELETE;
SELECT * FROM #Tab -- Output for debugging - delete in production.
END
CLOSE csrInput
DEALLOCATE csrInput
-- End Procedure Body.
SET #count += 1
END
Final output:
Store UpdateType ItemNumber Name
1 D 1 Small Wheel
1 D 4 Trunk

Group by numbers that are in sequence

I have some data like this:
row id
1 1
2 36
3 37
4 38
5 50
6 51
I would like to query it to look like this:
row id group
1 1 1
2 36 2
3 37 2
4 38 2
5 50 3
6 51 3
... so that I can GROUP BY where the numbers are consecutively sequential.
Also, looping/cursoring is out of the question since I'm working with a pretty large set of data, thanks.
;WITH firstrows AS
(
SELECT id, ROW_NUMBER() OVER (ORDER BY id) groupid
FROM Table1 a
WHERE id - 1 NOT IN (SELECT b.id FROM Table1 b)
)
SELECT id,
(
SELECT MAX(b.groupid)
FROM firstrows b
WHERE b.id <= a.id
) groupid
FROM Table1 a
with
data(row, id) as (
select *
from (
values
(1,1)
,(2,36)
,(3,37)
,(4,38)
,(5,50)
,(6,51)
) as foo(row, id)
),
anchor(row, id) as (
select row, id
from data d1
where not exists(select 0 from data d2 where d2.id = d1.id - 1)
)
select d1.*, dense_rank() over(order by foo.id) as thegroup
from
data d1
cross apply (select max(id) from anchor where anchor.id <= d1.id) as foo(id)
order by
d1.row
;
This solution does more work that is strictly necessary on the basis that there may be gaps in the sequence of row values, and on the assumption that those gaps should be ignored.
Set up test data:
DECLARE #table TABLE
(ROW INT,
id INT
)
INSERT #table
SELECT 1,1
UNION SELECT 2,36
UNION SELECT 3,37
UNION SELECT 4,38
UNION SELECT 5,50
UNION SELECT 6,51
Output query
;WITH grpCTE
AS
(
SELECT ROW, id,
ROW_NUMBER() OVER (ORDER BY ROW
) AS rn
FROM #table
)
,recCTE
AS
(
SELECT ROW, id, rn, 1 AS grp
FROM grpCTE
WHERE rn = 1
UNION ALL
SELECT g.row, g.id, g.rn, CASE WHEN g.id = r.id + 1 THEN r.grp ELSE r.grp + 1 END AS grp
FROM grpCTE AS g
JOIN recCTE AS r
ON g.rn = r.rn + 1
)
SELECT row, id, grp FROM recCTE
create table #temp
(
IDUnique int Identity(1,1),
ID int,
grp int
)
Insert into #temp(ID) Values(1)
Insert into #temp(ID) Values(36)
Insert into #temp(ID) Values(37)
Insert into #temp(ID) Values(38)
Insert into #temp(ID) Values(50)
Insert into #temp(ID) Values(51)
declare #IDUnique int
declare #PreviousUnique int
declare #ID int
declare #grp int
declare #Previous int
declare #Row int
DECLARE #getAccountID CURSOR SET #getAccountID = CURSOR FOR SELECT Row_Number() Over(Order by IDUnique) Row, IDUnique, ID From #temp
OPEN #getAccountID
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#Row = 1)
Begin
update #temp set grp = 1 Where IDUnique = #IDUnique
set #Previous = #ID
set #grp = 1
End
Else If (#Previous + 1 = #ID)
Begin
update #temp set grp = #grp Where IDUnique = #IDUnique
set #Previous = #ID
End
Else
Begin
set #Previous = #ID
set #grp = #grp + 1
update #temp set grp = #grp Where IDUnique = #IDUnique
End
FETCH NEXT FROM #getAccountID INTO #Row, #IDUnique, #ID
END
CLOSE #getAccountID
DEALLOCATE #getAccountID
Select * from #temp
Drop Table #temp
Select T.Id, T.Row, groupId as "Group", dr FROM tbrows T
Left Outer Join
(
Select min(id) as groupId,DENSE_RANK() over( order by min(id)) as dr, MIN(row-id) as d, Sum(1) as s FROM tbrows
Group BY (row-id)
) U
On (T.Id >= U.groupId) and (T.Id < U.groupId+U.s)
Order By T.Id

SQL query to find Missing sequence numbers

I have a column named sequence. The data in this column looks like 1, 2, 3, 4, 5, 7, 9, 10, 15.
I need to find the missing sequence numbers from the table. What SQL query will find the missing sequence numbers from my table? I am expecting results like
Missing numbers
---------------
6
8
11
12
13
14
I am using only one table. I tried the query below, but am not getting the results I want.
select de.sequence + 1 as sequence from dataentry as de
left outer join dataentry as de1 on de.sequence + 1 = de1.sequence
where de1.sequence is null order by sequence asc;
How about something like:
select (select isnull(max(val)+1,1) from mydata where val < md.val) as [from],
md.val - 1 as [to]
from mydata md
where md.val != 1 and not exists (
select 1 from mydata md2 where md2.val = md.val - 1)
giving summarised results:
from to
----------- -----------
6 6
8 8
11 14
I know this is a very old post but I wanted to add this solution that I found HERE so that I can find it easier:
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);
Try with this:
declare #min int
declare #max int
select #min = min(seq_field), #max = max(seq_field) from [Table]
create table #tmp (Field_No int)
while #min <= #max
begin
if not exists (select * from [Table] where seq_field = #min)
insert into #tmp (Field_No) values (#min)
set #min = #min + 1
end
select * from #tmp
drop table #tmp
The best solutions are those that use a temporary table with the sequence. Assuming you build such a table, LEFT JOIN with NULL check should do the job:
SELECT #sequence.value
FROM #sequence
LEFT JOIN MyTable ON #sequence.value = MyTable.value
WHERE MyTable.value IS NULL
But if you have to repeat this operation often (and more then for 1 sequence in the database), I would create a "static-data" table and have a script to populate it to the MAX(value) of all the tables you need.
SELECT CASE WHEN MAX(column_name) = COUNT(*)
THEN CAST(NULL AS INTEGER)
-- THEN MAX(column_name) + 1 as other option
WHEN MIN(column_name) > 1
THEN 1
WHEN MAX(column_name) <> COUNT(*)
THEN (SELECT MIN(column_name)+1
FROM table_name
WHERE (column_name+ 1)
NOT IN (SELECT column_name FROM table_name))
ELSE NULL END
FROM table_name;
Here is a script to create a stored procedure that returns missing sequential numbers for a given date range.
CREATE PROCEDURE dbo.ddc_RolledBackOrders
-- Add the parameters for the stored procedure here
#StartDate DATETIME ,
#EndDate DATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Min BIGINT
DECLARE #Max BIGINT
DECLARE #i BIGINT
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
CREATE TABLE #TempTable
(
TempOrderNumber BIGINT
)
SELECT #Min = ( SELECT MIN(ordernumber)
FROM dbo.Orders WITH ( NOLOCK )
WHERE OrderDate BETWEEN #StartDate AND #EndDate)
SELECT #Max = ( SELECT MAX(ordernumber)
FROM dbo.Orders WITH ( NOLOCK )
WHERE OrderDate BETWEEN #StartDate AND #EndDate)
SELECT #i = #Min
WHILE #i <= #Max
BEGIN
INSERT INTO #TempTable
SELECT #i
SELECT #i = #i + 1
END
SELECT TempOrderNumber
FROM #TempTable
LEFT JOIN dbo.orders o WITH ( NOLOCK ) ON tempordernumber = o.OrderNumber
WHERE o.OrderNumber IS NULL
END
GO
Aren't all given solutions way too complex?
wouldn't this be much simpler:
SELECT *
FROM (SELECT row_number() over(order by number) as N from master..spt_values) t
where N not in (select 1 as sequence union
select 2 union
select 3 union
select 4 union
select 5 union
select 7 union
select 10 union
select 15
)
This is my interpretation of this issue, placing the contents in a Table variable that I can easily access in the remainder of my script.
DECLARE #IDS TABLE (row int, ID int)
INSERT INTO #IDS
select ROW_NUMBER() OVER (ORDER BY x.[Referred_ID]), x.[Referred_ID] FROM
(SELECT b.[Referred_ID] + 1 [Referred_ID]
FROM [catalog].[dbo].[Referrals] b) as x
LEFT JOIN [catalog].[dbo].[Referrals] a ON x.[Referred_ID] = a.[Referred_ID]
WHERE a.[Referred_ID] IS NULL
select * from #IDS
Just for fun, I decided to post my solution.
I had an identity column in my table and I wanted to find missing invoice numbers.
I reviewed all the examples I could find but they were not elegant enough.
CREATE VIEW EENSkippedInvoicveNo
AS
SELECT CASE WHEN MSCNT = 1 THEN CAST(MSFIRST AS VARCHAR (8)) ELSE
CAST(MSFIRST AS VARCHAR (8)) + ' - ' + CAST(MSlAST AS VARCHAR (8)) END AS MISSING,
MSCNT, INV_DT FROM (
select invNo+1 as Msfirst, inv_no -1 as Mslast, inv_no - invno -1 as msCnt, dbo.fmtdt(Inv_dt) AS INV_dT
from (select inv_no as invNo, a4glidentity + 1 as a4glid
from oehdrhst_sql where inv_dt > 20140401) as s
inner Join oehdrhst_sql as h
on a4glid = a4glidentity
where inv_no - invno <> 1
) AS SS
DECLARE #MaxID INT = (SELECT MAX(timerecordid) FROM dbo.TimeRecord)
SELECT SeqID AS MissingSeqID
FROM (SELECT ROW_NUMBER() OVER (ORDER BY column_id) SeqID from sys.columns) LkUp
LEFT JOIN dbo.TimeRecord t ON t.timeRecordId = LkUp.SeqID
WHERE t.timeRecordId is null and SeqID < #MaxID
I found this answer here:
http://sql-developers.blogspot.com/2012/10/how-to-find-missing-identitysequence.html
I was looking for a solution and found many answers. This is the one I used and it worked very well. I hope this helps anyone looking for a similar answer.
-- This will return better Results
-- ----------------------------------
;With CTERange
As (
select (select isnull(max(ArchiveID)+1,1) from tblArchives where ArchiveID < md.ArchiveID) as [from],
md.ArchiveID - 1 as [to]
from tblArchives md
where md.ArchiveID != 1 and not exists (
select 1 from tblArchives md2 where md2.ArchiveID = md.ArchiveID - 1)
) SELECT [from], [to], ([to]-[from])+1 [total missing]
From CTERange
ORDER BY ([to]-[from])+1 DESC;
from to total missing
------- ------- --------------
6 6 1
8 8 1
11 14 4
DECLARE #TempSujith TABLE
(MissingId int)
Declare #Id Int
DECLARE #mycur CURSOR
SET #mycur = CURSOR FOR Select Id From tbl_Table
OPEN #mycur
FETCH NEXT FROM #mycur INTO #Id
Declare #index int
Set #index = 1
WHILE ##FETCH_STATUS = 0
BEGIN
if (#index < #Id)
begin
while #index < #Id
begin
insert into #TempSujith values (#index)
set #index = #index + 1
end
end
set #index = #index + 1
FETCH NEXT FROM #mycur INTO #Id
END
Select Id from tbl_Table
select MissingId from #TempSujith
Create a useful Tally table:
-- can go up to 4 million or 2^22
select top 100000 identity(int, 1, 1) Id
into Tally
from master..spt_values
cross join master..spt_values
Index it, or make that single column as PK.
Then use EXCEPT to get your missing number.
select Id from Tally where Id <= (select max(Id) from TestTable)
except
select Id from TestTable
You could also solve using something like a CTE to generate the full sequence:
create table #tmp(sequence int)
insert into #tmp(sequence) values (1)
insert into #tmp(sequence) values (2)
insert into #tmp(sequence) values (3)
insert into #tmp(sequence) values (5)
insert into #tmp(sequence) values (6)
insert into #tmp(sequence) values (8)
insert into #tmp(sequence) values (10)
insert into #tmp(sequence) values (11)
insert into #tmp(sequence) values (14)
DECLARE #max INT
SELECT #max = max(sequence) from #tmp;
with full_sequence
(
Sequence
)
as
(
SELECT 1 Sequence
UNION ALL
SELECT Sequence + 1
FROM full_sequence
WHERE Sequence < #max
)
SELECT
full_sequence.sequence
FROM
full_sequence
LEFT JOIN
#tmp
ON
full_sequence.sequence = #tmp.sequence
WHERE
#tmp.sequence IS NULL
Hmmmm - the formatting is not working on here for some reason? Can anyone see the problem?
i had made a proc so you can send the table name and the key and the result is a list of missing numbers from the given table
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create PROCEDURE [dbo].[action_FindMissing_Autoincremnt]
(
#tblname as nvarchar(50),
#tblKey as nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
declare #qry nvarchar(4000)
set #qry = 'declare #min int '
set #qry = #qry + 'declare #max int '
set #qry = #qry +'select #min = min(' + #tblKey + ')'
set #qry = #qry + ', #max = max('+ #tblKey +') '
set #qry = #qry + ' from '+ #tblname
set #qry = #qry + ' create table #tmp (Field_No int)
while #min <= #max
begin
if not exists (select * from '+ #tblname +' where '+ #tblKey +' = #min)
insert into #tmp (Field_No) values (#min)
set #min = #min + 1
end
select * from #tmp order by Field_No
drop table #tmp '
exec sp_executesql #qry
END
GO
SELECT TOP 1 (Id + 1)
FROM CustomerNumberGenerator
WHERE (Id + 1) NOT IN ( SELECT Id FROM CustomerNumberGenerator )
Working on a customer number generator for my company. Not the most efficient but definitely most readable
The table has one Id column.
The table allows for Ids to be inserted at manually by a user off sequence.
The solution solves the case where the user decided to pick a high number