SQL Server 2008 filling gaps with dimension - sql

I have a data table as below
#data
---------------
Account AccountType
---------------
1 2
2 0
3 5
4 2
5 1
6 5
AccountType 2 is headers and 5 is totals. Meaning accounts of type 2 have to look after the next 1 or 0 to determin if its Dim value is 1 or 0. Totals of type 5 have to look up at nearest 1 or 0 to determin its Dim value. Accounts of type 1 or 0 have there type as Dim.
Accounts of type 2 appear as islands so its not enough to just check RowNumber + 1 and same goes for accounsts of type 5.
I have arrived at the following table using CTE's. But can't find a quick way to go from here to my final result of Account, AccountType, Dim for all accounts
T3
-------------------
StartRow EndRow AccountType Dim
-------------------
1 1 2 0
2 2 0 0
3 3 5 0
4 4 2 1
5 5 0 1
6 6 5 1
Below code is MS TSQL copy paste it all and see it run. The final join on the CTE select statement is extremly slow for even 500 rows it takes 30 sec. I have 100.000 rows i need to handle. I done a cursor based solution which do it in 10-20 sec thats workable and a fast recursive CTE solution that do it in 5 sec for 100.000 rows, but it dependent on the fragmentation of the #data table. I should add this is simplified the real problem have alot more dimension that need to be taking into account. But it will work the same for this simple problem.
Anyway is there a fast way to do this using joins or another set based solution.
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
CREATE TABLE #data
(
Account INTEGER IDENTITY(1,1),
AccountType INTEGER,
)
BEGIN -- TEST DATA
DECLARE #Counter INTEGER = 0
DECLARE #MaxDataRows INTEGER = 50 -- Change here to check performance
DECLARE #Type INTEGER
WHILE(#Counter < #MaxDataRows)
BEGIN
SET #Type = CASE
WHEN #Counter % 10 < 3 THEN 2
WHEN #Counter % 10 >= 8 THEN 5
WHEN #Counter % 10 >= 3 THEN (CASE WHEN #Counter < #MaxDataRows / 2.0 THEN 0 ELSE 1 END )
ELSE 0
END
INSERT INTO #data VALUES(#Type)
SET #Counter = #Counter + 1
END
END -- TEST DATA END
;WITH groupIds_cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY AccountType ORDER BY Account) - Account AS GroupId
FROM #data
),
islandRanges_cte AS
(
SELECT
MIN(Account) AS StartRow,
MAX(Account) AS EndRow,
AccountType
FROM groupIds_cte
GROUP BY GroupId,AccountType
),
T3 AS
(
SELECT I.*, J.AccountType AS Dim
FROM islandRanges_cte I
INNER JOIN islandRanges_cte J
ON (I.EndRow + 1 = J.StartRow AND I.AccountType = 2)
UNION ALL
SELECT I.*, J.AccountType AS Dim
FROM islandRanges_cte I
INNER JOIN islandRanges_cte J
ON (I.StartRow - 1 = J.EndRow AND I.AccountType = 5)
UNION ALL
SELECT *, AccountType AS Dim
FROM islandRanges_cte
WHERE AccountType = 0 OR AccountType = 1
),
T4 AS
(
SELECT Account, Dim
FROM (
SELECT FlattenRow AS Account, StartRow, EndRow, Dim
FROM T3 I
CROSS APPLY (VALUES(StartRow),(EndRow)) newValues (FlattenRow)
) T
)
--SELECT * FROM T3 ORDER BY StartRow
--SELECT * FROM T4 ORDER BY Account
-- Final correct result but very very slow
SELECT D.Account, D.AccountType, I.Dim FROM T3 I
INNER JOIN #data D
ON D.Account BETWEEN I.StartRow AND I.EndRow
ORDER BY Account
EDIT with some time testing
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#data') IS NULL
CREATE TABLE #times
(
RecId INTEGER IDENTITY(1,1),
Batch INTEGER,
Method NVARCHAR(255),
MethodDescription NVARCHAR(255),
RunTime INTEGER
)
IF OBJECT_ID('tempdb..#batch') IS NULL
CREATE TABLE #batch
(
Batch INTEGER IDENTITY(1,1),
Bit BIT
)
INSERT INTO #batch VALUES(0)
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
CREATE TABLE #data
(
Account INTEGER
)
CREATE NONCLUSTERED INDEX data_account_index ON #data (Account)
IF OBJECT_ID('tempdb..#islands') IS NOT NULL
DROP TABLE #islands
CREATE TABLE #islands
(
AccountFrom INTEGER ,
AccountTo INTEGER,
Dim INTEGER,
)
CREATE NONCLUSTERED INDEX islands_from_index ON #islands (AccountFrom, AccountTo, Dim)
BEGIN -- TEST DATA
INSERT INTO #data
SELECT TOP 100000 ROW_NUMBER() OVER(ORDER BY t1.number) AS N
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
INSERT INTO #islands
SELECT MIN(Account) AS Start, MAX(Account), Grp
FROM (SELECT *, NTILE(10) OVER (ORDER BY Account) AS Grp FROM #data) T
GROUP BY Grp ORDER BY Start
END -- TEST DATA END
--SELECT * FROM #data
--SELECT * FROM #islands
--PRINT CONVERT(varchar(20),DATEDIFF(MS,#RunDate,GETDATE()))+' ms Sub Query'
DECLARE #RunDate datetime
SET #RunDate=GETDATE()
SELECT Account, (SELECT Dim From #islands WHERE Account BETWEEN AccountFrom AND AccountTo) AS Dim
FROM #data
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'subquery','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
SELECT D.Account, V.Dim
FROM #data D
CROSS APPLY
(
SELECT Dim From #islands V
WHERE D.Account BETWEEN V.AccountFrom AND V.AccountTo
) V
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'crossapply','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
SELECT D.Account, I.Dim
FROM #data D
JOIN #islands I
ON D.Account BETWEEN I.AccountFrom AND I.AccountTo
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'join','',DATEDIFF(MS,#RunDate,GETDATE()))
SET #RunDate=GETDATE()
;WITH cte AS
(
SELECT Account, AccountFrom, AccountTo, Dim, 1 AS Counting
FROM #islands
CROSS APPLY (VALUES(AccountFrom),(AccountTo)) V (Account)
UNION ALL
SELECT Account + 1 ,AccountFrom, AccountTo, Dim, Counting + 1
FROM cte
WHERE (Account + 1) > AccountFrom AND (Account + 1) < AccountTo
)
SELECT Account, Dim, Counting FROM cte OPTION(MAXRECURSION 32767)
INSERT INTO #times VALUES ((SELECT MAX(Batch) FROM #batch) ,'recursivecte','',DATEDIFF(MS,#RunDate,GETDATE()))
You can select from the #times table to see the run times :)

I think you want a join, but using an inequality rather than an equality:
select tt.id, tt.dim1, it.dim2
from TallyTable tt join
IslandsTable it
on tt.id between it."from" and it."to"
This works for the data that you provide in the question.
Here is another idea that might work. Here is the query:
select d.*,
(select top 1 AccountType from #data d2 where d2.Account > d.Account and d2.AccountType not in (2, 5)
) nextAccountType
from #data d
order by d.account;
I just ran this on 50,000 rows and this version took 17 seconds on my system. Changing the table to:
CREATE TABLE #data (
Account INTEGER IDENTITY(1,1) primary key,
AccountType INTEGER,
);
Has actually slowed it down to about 1:33 -- quite to my surprise. Perhaps one of these will help you.

Related

Sql how to convert nest while loops to a CTE using two temp tables

I have one temp table that pulls from the Abstracting table for dx codes and Registration table for the account number. It joins by the VisitID and the dx codes are in order by DiagnosisUrn - #Visits
I need to join it to the DiagTable. The dx codesin this table are the primary ones for billing, in
order by Transaction Number ID with the lowest number being the most primary.
In the end, for each visit id , account id, I need to for the Dx codes to print out
like this example:
Account ICD10 SequenceNumber
AT000118 R06.00 1
AT000118 I25.10 2
AT000118 I65.23 3
AT000118 E11.29 4
AT000118 Z95.1 5
AT000118 E11.65 6
AT000118 E78.2 7
AT000118 E78.1 8
AT000118 E11.42 9
For this particular account (there are many others similar), all of the dx are in the Visit table
however only the first four are in Diag table with the proper TransactionDiagnosisURN/DiagnosisURN/Sequence Number
The While loops reorder the rest of DX codes that were in the visit temp table and adding them to the end of the ones in the Diag table with the result populating the PBDxCodes temp table.
It needs to run by date range - I've changed it to pull a few account numbers instead to try to work on
a CTE. Using loops causes it to run an extremely long time and sometimes it just times out.
I'm getting a recursion error when trying to convert the query to CTE. I have only used basic CTE in the past and it has been a while.
Below is the query using the while loop and then my attempt at trying to convert one loop to a CTE:
While loops:
DECLARE #NumofVisits AS INT
DECLARE #NumberofTxs AS INT
DECLARE #Looper AS INT
DECLARE #ThisVisitID AS VARCHAR(35)
--Get the VisitIDs to find Dx Codes
DROP TABLE IF EXISTS #Visits
SELECT DISTINCT
RAM.VisitID
,RAM.AccountNumber
,ADX.DiagnosisCode_MisDxID
,CAST(ADX.DiagnosisUrnID AS INT) AS ABSDxCode
INTO #Visits
FROM livefdb.dbo.AbsAcct_Diagnoses ADX
JOIN livefdb.dbo.RegAcct_Main AS RAM
ON RAM.VisitID = ADX.VisitID
AND RAM.SourceID = ADX.SourceID
AND RAM.Facility_MisFacID NOT IN ('KK', 'JFJ','MPM')
AND RAM.RegistrationType_MisRegTypeID IN ('AMB','AMBR','BNV')
LEFT JOIN livefdb.dbo.HimRec_Main AS HRM
ON HRM.SourceID = RAM.SourceID
AND HRM.PatientID = RAM.PatientID
WHERE ADX.VisitID IN ('LE1-B20210114114749175', 'AT0-B20191211155403251'
,'ID0-B20201201104450465','OM1-B20210107093907435'
,'AT0-B20191205154143298','LE0-B20200716135146384'
,'PC1-B20210111125154209','SV1-B20210112122435108'
,'UB0-B20200915140417079','ID1-B20201222150226545'
)
SET #NumofVisits = (SELECT COUNT(DISTINCT VisitID) FROM #Visits )
-- Create a table to hold Dx data for each visit
DROP TABLE IF EXISTS #PBDxCodes
CREATE TABLE #PBDxCodes (
EncounterRecordNumber VARCHAR(18),
ICD10DiagnosisCode VARCHAR(15),
SequenceNumber INT,
)
-- Get the Diagnosis from BAR Transactions. The least transaction number is the Primary Charge
-- and the Diagnosis Codes are the Primary Diagnosis codes
WHILE #NumofVisits > 0
BEGIN
DROP TABLE IF EXISTS #UpdateDiag
CREATE TABLE #UpdateDiag (
DiagnosisUrnID INT IDENTITY(1,1) PRIMARY KEY,
VisitID VARCHAR(35),
Diagnosis_MisDxID VARCHAR(50)
)
SET #ThisVisitID = (SELECT TOP 1 VisitID FROM #Visits)
SELECT DISTINCT
BATD.VisitID,
BATD.TransactionNumberID,
CAST(BATD.TransactionDiagnosisUrnID AS INT) AS TransactionDiagnosisUrnID,
BATD.TransactionDiagnosis_MisDxID
INTO #Diag
FROM livefdb.dbo.BarAcct_TxnDiagnoses AS BATD
WHERE BATD.VisitID = #ThisVisitID
SET #NumberofTxs = (SELECT COUNT(DISTINCT TransactionNumberID) FROM #Diag)
SET #Looper = 0
WHILE ( #NumberofTxs > #Looper)
BEGIN
INSERT INTO #UpdateDiag(VisitID, Diagnosis_MisDxID)
SELECT
VisitID,
TransactionDiagnosis_MisDxID
FROM #Diag
WHERE TransactionNumberID =
(CASE WHEN 1 = (SELECT COUNT(DISTINCT TransactionNumberID) FROM #Diag)
THEN (SELECT TOP 1 TransactionNumberID FROM #Diag )
ELSE (SELECT MIN(TransactionNumberID) FROM #Diag)
END)
AND TransactionDiagnosis_MisDxID NOT IN (SELECT Diagnosis_MisDxID FROM #UpdateDiag)
ORDER BY TransactionDiagnosisUrnID
DELETE FROM #Diag WHERE TransactionNumberID = (SELECT MIN(TransactionNumberID) FROM #Diag)
SET #Looper = #Looper + 1
END
-- Are there Diagnosis in Abstracting that are not in BAR
INSERT INTO #UpdateDiag(VisitID, Diagnosis_MisDxID)
SELECT
VisitID,
DiagnosisCode_MisDxID
FROM #Visits
WHERE #Visits.VisitID = #ThisVisitID
AND #Visits.DiagnosisCode_MisDxID NOT IN (SELECT Diagnosis_MisDxID FROM #UpdateDiag)
ORDER BY #Visits.ABSDxCode
-- Add this visit to the temp table holding final results
INSERT INTO #PBDxCodes (EncounterRecordNumber,SequenceNumber,ICD10DiagnosisCode,SourceSystem)
SELECT DISTINCT
#Visits.AccountNumber AS EncounterRecordNumber,
#UpdateDiag.DiagnosisUrnID AS SequenceNumber,
#UpdateDiag.Diagnosis_MisDxID AS ICD10DiagnosisCode
FROM #UpdateDiag
INNER JOIN #Visits ON #UpdateDiag.VisitID = #Visits.VisitID
DELETE FROM #Visits WHERE VisitID = #ThisVisitID
SET #NumofVisits = #NumofVisits - 1
DROP TABLE #Diag, #UpdateDiag
END
SELECT * FROM #PBDxCodes
WHERE EncounterRecordNumber='AT0001184503'
ORDER BY EncounterRecordNumber, SequenceNumber
DROP TABLE #Visits, #PBDxCodes
CTE Attempt:
DECLARE #NumofVisits AS INT
DECLARE #NumberofTxs AS INT
DECLARE #Looper AS INT
DECLARE #ThisVisitID AS VARCHAR(35)
--Get VisitID,Account Nos and Dx in Abstracting.
DROP TABLE IF EXISTS #Visits
SELECT DISTINCT
RAM.VisitID
,RAM.AccountNumber
,ADX.DiagnosisCode_MisDxID
,CAST(ADX.DiagnosisUrnID AS INT) AS ABSDxCode
INTO #Visits
FROM livefdb.dbo.AbsAcct_Diagnoses ADX
JOIN livefdb.dbo.RegAcct_Main RAM
ON ADX.SourceID=RAM.SourceID
AND ADX.VisitID=RAM.VisitID
AND RAM.Facility_MisFacID NOT IN ('KK', 'JFJ','MPM')
AND RAM.RegistrationType_MisRegTypeID IN ('AMB','AMBR','BNV')
WHERE ADX.VisitID IN ('AT0-B20191211155403251','LE1-B20210114114749175')
SET #NumofVisits = (SELECT COUNT(DISTINCT VisitID) FROM #Visits);
WITH CTE AS (
SELECT
V.ABSDxCode
,#NumofVisits AS n
,V.VisitID
,V.DiagnosisCode_MisDxID
FROM #Visits V
UNION ALL
SELECT
C.ABSDxCode
,C.n+1
,V.VisitID AS VisitID
,V.DiagnosisCode_MisDxID
FROM CTE C
INNER JOIN #Visits AS V ON V.VisitID=C.VisitID
)
SELECT C.ABSDxCode, C.DiagnosisCode_MisDxID
FROM CTE C
WHERE C.n=(SELECT MAX(n) FROM CTE WHERE ABSDxCode=C.ABSDxCode)

SQL multiplying rows in select

I would like to select some rows multiple-times, depending on the column's value.
Source table
Article | Count
===============
A | 1
B | 4
C | 2
Wanted result
Article
===============
A
B
B
B
B
C
C
Any hints or samples, please?
You could use:
SELECT m.Article
FROM mytable m
CROSS APPLY (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS s(n)
WHERE s.n <= m.[Count];
LiveDemo
Note: CROSS APLLY with any tally table. Here values up to 10.
Related: What is the best way to create and populate a numbers table?
You could also use a recursive CTE which works with numbers > 10 (here up to 1000):
With NumberSequence( Number ) as
(
Select 0 as Number
union all
Select Number + 1
from NumberSequence
where Number BETWEEN 0 AND 1000
)
SELECT Article
FROM ArticleCounts
CROSS APPLY NumberSequence
WHERE Number BETWEEN 1 AND [Count]
ORDER BY Article
Option (MaxRecursion 0)
Demo
A number-table will certainly be the best option.
http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-2
Please check following SQL script
Before executing the SELECT statement, note that I used a user function which is used to simulate a numbers table
You can find the sql codes of numbers table in SQL Server at referred tutorial
----create table myTempTbl (Article varchar(10), Count int)
--insert into myTempTbl select 'A',1
--insert into myTempTbl select 'B',4
--insert into myTempTbl select 'C',2
select t.*
from myTempTbl t
cross apply dbo.NumbersTable(1,100,1) n
where n.i <= t.Count
order by t.Article
one more CTE
with cte_t as (
select c as c, 1 as i
from mytable
group by c
union all
select t.c, ctet.i + 1
from mytable t
join cte_t ctet
on ctet.c = t.c
and ctet.i < t.i
)
select cte_t.c
from cte_t
order by cte_t.c
Can obtain the output using simple WHILE LOOP
DECLARE #table TABLE
(ID int ,Article varchar(5),[Count] int)
INSERT INTO #table
(ID,Article,Count)
VALUES
(1,'A',1),(2,'B',4),(3,'C',2)
DECLARE #temp TABLE
(Article varchar(5))
DECLARE #Cnt1 INT
DECLARE #Cnt2 INT
DECLARE #Check INT
DECLARE #max INT
SET #max =0
SET #Cnt1 = (SELECT Count(Article) FROM #table)
WHILE (#max < #Cnt1)
BEGIN
SET #max = #max +1
SET #Cnt2 = (SELECT [Count] FROM #table WHERE ID =#max)
SET #Check =(SELECT [Count] FROM #table WHERE ID =#max)
WHILE (#Cnt2 > 0)
BEGIN
INSERT INTO #temp
SELECT Article FROM #table WHERE [Count] =#Check
SET #Cnt2 = #Cnt2 -1
END
END
SELECT * FROM #temp

Select non-existing rows

Let say I have a table:
ColumnA ColumnB
---------------------------------
1 10.75
4 1234.30
6 2000.99
How can I write a SELECT query that will result in the following:
ColumnA ColumnB
---------------------------------
1 10.75
2 0.00
3 0.00
4 1234.30
5 0.00
6 2000.99
You can use a CTE to create a list of numbers from 1 to the maximum value in your table:
; with numbers as
(
select max(ColumnA) as nr
from YourTable
union all
select nr - 1
from numbers
where nr > 1
)
select nr.nr as ColumnA
, yt.ColumnB
from numbers nr
left join
YourTable yt
on nr.nr = yt.ColumnA
order by
nr.nr
option (maxrecursion 0)
See it working at SQL Fiddle.
Please try:
declare #min int, #max int
select #min=MIN(ColumnA), #max=MAX(ColumnA) from tbl
select
distinct number ColumnA,
isnull(b.ColumnB, 0) ColumnB
from
master.dbo.spt_values a left join tbl b on a.number=b.ColumnA
where number between #min and #max
Create a TallyTable (or NumbersTable) - see this question: What is the best way to create and populate a numbers table?
With that table create an insert statement:
INSERT INTO YourTable (ColumnA, ColumnB)
SELECT Number FROM NumberTable
WHERE
NOT EXISTS (SELECT 1 FROM YourTable WHERE NumberTable.Number = YourTable.ColumnA)
-- Adjust this value or calculate it with a query to the maximum of the source table
AND NumberTable.Number < 230130
DECLARE #t TABLE (ID INT,Val DECIMAL(10,2))
INSERT INTO #t (ID,Val) VALUES (1,10.75)
INSERT INTO #t (ID,Val) VALUES (4,6.75)
INSERT INTO #t (ID,Val) VALUES (7,4.75)
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 1
set #MaxNo = 10
set #IncrementStep = 1
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num,
CASE WHEN Val IS NOT NULL THEN Val ELSE 0.00 END AS NUMBER
from C
LEFT JOIN #t t
ON t.ID = c.Num
You could use a number-table or following trick to generate a sequence which you can LEFT OUTER JOIN with your table. I assume you want to determine the boundaries dynamically:
WITH Seq AS
(
SELECT TOP ((SELECT Max(ColumnA)FROM Table1) - (SELECT Min(ColumnA) FROM Table1) + 1)
Num = (SELECT Min(ColumnA) FROM Table1)+ Row_number() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects)
SELECT ColumnA = Seq.Num,
ColumnB = COALESCE(t.ColumnB ,0.00)
FROM Seq
LEFT OUTER JOIN Table1 t
ON Seq.Num = t.ColumnA
Demo with your sample.
Worth reading: http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I have my collect of table functions like these.
create function dbo.GetNumbers(#Start int, #End int)
returns #Items table
(
Item int
)
as
begin
while (#Start <= #End)
begin
insert into #Items
values (#Start)
set #Start = #Start + 1
end
return
end
Then I can use it to left join to my data table and every value will be there.
declare #min int, #max int
set #min = 10
set #max = 20
select gn.Item
from dbo.GetNumbers(#min, #max) gn
I have similar table functions for date ranges, times, timezones, etc.

SQL to return integer values not used within a particular range

Is it possible to use SQL to return a list of values that are NOT in a particular dataset but are within the range of values?
For example, I have a column with the following....
Registration_no
1
5
6
9
I would like to return all the 'unused' integers between the min and the max in the range, which in this example would 2, 3, 4, 7 and 8
Is this possible by way of a SQL statement?
Many thanks,
You cannot get them as a list of values using generic SQL. But, you can get them as ranges.
select registration_no + 1 as StartMissingRange, registration_no - 1 as EndMissingRange
from (select r.*,
(select min(registration_no)
from registrations r2
where r2.registration_no > r.registration_no
) as next_ro
from registrations r
) t
where next_ro <> registration_no + 1;
This is generic SQL. There are other solutions depending on the database (such as using lead() function). If you want the list with separate numbers on each row, then a recursive CTE is one method supported by many databases.
CREATE TABLE [table]
(number varchar(250))
INSERT INTO [table] VALUES (1),(5),(6),(9)
SELECT * FROM [table]
DEClARE #a varchar(250)
SET #a = (SELECT MIN(number) FROM [table])
WHILE (SELECT MAX(number) FROM [table] ) > #a
BEGIN
IF #a NOT IN ( SELECT number FROM [table] )
PRINT #a
SET #a=#a+1
END
This will give you the missing numbers....2,3,4,7,8
Test Data
DECLARE #TABLE TABLE(registration_no INT)
INSERT INTO #TABLE VALUES
(1),(5),(6),(9)
Query
;WITH CTE AS
(
SELECT MIN(registration_no) MinVal
,MAX(registration_no) MaxVal
FROM #TABLE
)
,
RequiredVals
AS
(
SELECT DISTINCT [number]
FROM master..spt_values
WHERE number >= (SELECT MinVal FROM CTE)
AND number <= (SELECT MaxVal FROM CTE)
AND NOT EXISTS (SELECT 1
FROM #TABLE
WHERE registration_no = spt_values.number)
)
SELECT number AS MissingValues
FROM RequiredVals
Result Set
╔═══════════════╗
║ MissingValues ║
╠═══════════════╣
║ 2 ║
║ 3 ║
║ 4 ║
║ 7 ║
║ 8 ║
╚═══════════════╝
You can do this with a numbers table in regular SQL I believe. The idea is you have a table with a column that just lists all integers. Then you join to that table to find the missing numbers. Basically a poor mans sequence.
CREATE TABLE `Numbers` (
`NUM` int(11) NOT NULL,
PRIMARY KEY (`NUM`)
) ;
Fill the table with integers.
SELECT NUM
FROM
(SELECT MIN(Registration_no) AS MIN_Range, Max(Registration_no) AS Max_Range
FROM StackOverflow.Registration) r_temp
INNER JOIN
StackOverflow.Numbers n
ON
n.NUM >= r_temp.MIN_Range AND n.NUM <= r_temp.MAX_Range
LEFT OUTER JOIN
StackOverflow.Registration r
ON
r.Registration_no = n.NUM
WHERE r.Registration_no IS NULL
;
*http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
Simply do this:
DECLARE #MinVal int
DECLARE #MaxVal int
SELECT #MinVal=Min(registration_no),#MaxVal=Max(registration_no) FROM TableName
;WITH nums AS
(SELECT #MinVal AS value
UNION ALL
SELECT value + 1 AS value
FROM nums
WHERE nums.value <#MaxVal)
SELECT *
FROM nums
WHERE value NOT IN (SELECT registration_no FROM TableName)
Result:
VALUE
2
3
4
7
8
See result in SQL Fiddle.
Just Update the "Range" Below (note: code will only work up to the size of sys.columns table)
/* TABLE OF VALUES */
CREATE TABLE [#table](number varchar(250))
INSERT INTO [#table]
SELECT 1 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 9
select * from
(
select row=row_number() OVER (ORDER BY name) from sys.columns
)a
where row BETWEEN 1 and 20 /*CHANGE THIS RANGE*/
and row not in
(
SELECT number FROM [#table] /* TABLE OF VALUES */
)
DROP TABLE [#table]

Sequential numbers randomly selected and added to table

The SO Question has lead me to the following question.
If a table has 16 rows I'd like to add a field to the table with the numbers 1,2,3,4,5,...,16 arranged randomly i.e in the 'RndVal' field for row 1 this could be 2, then for row 2 it could be 5 i.e each of the 16 integers needs to appear once without repetition.
Why doesn't the following work? Ideally I'd like to see this working then to see alternative solutions.
This creates the table ok:
IF OBJECT_ID('tempdb..#A') IS NOT NULL BEGIN DROP TABLE #A END
IF OBJECT_ID('tempdb..#B') IS NOT NULL BEGIN DROP TABLE #B END
IF OBJECT_ID('tempdb..#C') IS NOT NULL BEGIN DROP TABLE #C END
IF OBJECT_ID('tempdb..#myTable') IS NOT NULL BEGIN DROP TABLE #myTable END
CREATE TABLE #B (B_ID INT)
CREATE TABLE #C (C_ID INT)
INSERT INTO #B(B_ID) VALUES
(10),
(20),
(30),
(40)
INSERT INTO #C(C_ID)VALUES
(1),
(2),
(3),
(4)
CREATE TABLE #A
(
B_ID INT
, C_ID INT
, RndVal INT
)
INSERT INTO #A(B_ID, C_ID, RndVal)
SELECT
#B.B_ID
, #C.C_ID
, 0
FROM #B CROSS JOIN #C;
Then I'm attempting to add the random column using the following. The logic is to add random numbers between 1 and 16 > then to effectively overwrite any that are duplicated with other numbers > in a loop ...
SELECT
ROW_NUMBER() OVER(ORDER BY B_ID) AS Row
, B_ID
, C_ID
, RndVal
INTO #myTable
FROM #A
DECLARE #rowsRequired INT = (SELECT COUNT(*) CNT FROM #myTable)
DECLARE #i INT = (SELECT #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable)--0
DECLARE #end INT = 1
WHILE #end > 0
BEGIN
SELECT #i = #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable
WHILE #i>0
BEGIN
UPDATE x
SET x.RndVal = FLOOR(RAND()*#rowsRequired)
FROM #myTable x
WHERE x.RndVal = 0
SET #i = #i-1
END
--this is to remove possible duplicates
UPDATE c
SET c.RndVal = 0
FROM
#myTable c
INNER JOIN
(
SELECT RndVal
FROM #myTable
GROUP BY RndVal
HAVING COUNT(RndVal)>1
) t
ON
c.RndVal = t.RndVal
SET #end = ##ROWCOUNT
END
TRUNCATE TABLE #A
INSERT INTO #A
SELECT
B_ID
, C_ID
, RndVal
FROM #myTable
If the original table has 6 rows then the result should end up something like this
B_ID|C_ID|RndVal
----------------
| | 5
| | 4
| | 1
| | 6
| | 3
| | 2
I don't understand your code, frankly
This will update each row with a random number, non-repeated number between 1 and the number of rows in the table
UPDATE T
SET SomeCol = T2.X
FROM
MyTable T
JOIN
(
SELECT
KeyCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T2 ON T.KeyCol = T2.KeyCol
This is more concise but can't test to see if it works as expected
UPDATE T
SET SomeCol = X
FROM
(
SELECT
SomeCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T
When you add TOP (1) (because you need to update first RndVal=0 record) and +1 (because otherwise your zero mark means nothing) to your update, things will start to move. But extremely slowly (around 40 seconds on my rather outdated laptop). This is because, as #myTable gets filled with generated random numbers, it becomes less and less probable to get missing numbers - you usually get duplicate, and have to start again.
UPDATE top (1) x
SET x.RndVal = FLOOR(RAND()*#rowsRequired) + 1
FROM #myTable x
WHERE x.RndVal = 0
Of course, #gbn has perfectly valid solution.
This is basically the same as the previous answer, but specific to your code:
;WITH CTE As
(
SELECT B_ID, C_ID, RndVal,
ROW_NUMBER() OVER(ORDER BY NewID()) As NewOrder
FROM #A
)
UPDATE CTE
SET RndVal = NewOrder
SELECT * FROM #A ORDER BY RndVal