Related
How to generate 6 digit unique alphanumeric string with 6- character length, case nonsensitive for 4 million records. By replacing 1’s, I’s, O’s, and 0’s.
I have tried using the below query but the problem is when I am trying to replace the above values the unique id has some duplicate values.
**
select CAST(REPLACE(REPLACE(CHAR( ASCII('AA')+(ABS(CHECKSUM(NEWID()))%25)) , 'O', ''), 'I', '')
REPLACE(REPLACE(REPLACE( REPLACE(SUBSTRING(CONVERT(varchar(60), NEWID()),1, 10) , '-',''), '.' , ''), '0' , ''), '1','') AS nvarchar (6))
, employee_id
from cte
**
The final output should be something like:
....
....
....
...
....
....
....
...
....
....
...
How to generate 6 digit unique alphanumeric string with 6- character length, case nonsensitive for 4 million records.
There are enough hex digits to do what you want. So, one option is:
select right('ZZZZZZ' + format(row_number() over (order by newid()), 'X'), 6)
This generates a sequential number (randomly), converts it to hex, and then prepends Zs.
If you want the UIDs to appear to be random (e.g., 1st could be G5K2M5, second 23BN32, etc), I think you basically have three choices
(In a loop) randomly generating UIDs, remove those that a) already exist, and b) have duplicates in your generated list, then insert the unique UIDs. Repeat until you have none left.
Generate a table with all possible UIDs (e.g., all letters and numbers except 1, I, L, 0, o - note I've added L to the list as lowercase l looks like I or 1). That means 31 possible characters in 6 slots... 31^6 is approximately 900 million possibilities. For the UIDs to use, randomly select the number needed from the UID list, assign them as needed, then remove them from the list so you won't get doubles.
Use a formula where each number is uniquely mapped to a UID. Then just get the rownumber or other unique int identifier, and calculate the UID from it. Note that the formula could be a mathematical formula, or could just be a table (as above) where the UIDs are initially randomly sorted, and you just take the UID from the relevant rownumber.
select top (100000)
cte.*,
concat
(
substring(s.random32, p.p1, 1),
substring(s.random32, p.p2, 1),
substring(s.random32, p.p3, 1),
substring(s.random32, p.p4, 1),
substring(s.random32, p.p5, 1),
substring(s.random32, p.p6, 1)
) as combo6
from
--employees
(
--4mil employees
select top (4000000)
row_number() over(order by ##spid) as empid, --this could be empid, eg. empid as n
a.name as empfirstname, a.name as emplastname, b.type_desc as emptype
from sys.all_objects as a
cross join sys.all_objects as b
) as cte
--random string
cross join
(
--one random string (excluding 1, 0, I, O)
select top (1)
(
select v.v as '*'
from
(values
('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),
('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),
('J'),('K'),('L'),('M'),('N'), ('P'),('Q'),('R'),
('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z')
) as v(v)
order by newid()
for xml path('')
) as random32
) as s
--combo6 positions in string
cross apply
(
select
/*for 32 chars = len(rand32) */
(power(32,0)+(cte.empid-1)%power(32, 1))/power(32,0) as p1,
(power(32,1)+(cte.empid-1)%power(32, 2))/power(32,1) as p2,
(power(32,2)+(cte.empid-1)%power(32, 3))/power(32,2) as p3,
(power(32,3)+(cte.empid-1)%power(32, 4))/power(32,3) as p4,
(power(32,4)+(cte.empid-1)%power(32, 5))/power(32,4) as p5,
(power(32,5)+(cte.empid-1)%power(32, 6))/power(32,5) as p6
) as p
go
....or....(?)
create or alter function dbo.[why?]()
returns char(6)
as
begin
declare #combo6 char(6);
declare #randomstring char(32) = cast(session_context(N'randomstring') as char(32));
if #randomstring is null
begin
select #randomstring =
(
select v.v as '*'
from
(values
('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),
('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),
('J'),('K'),('L'),('M'),('N'), ('P'),('Q'),('R'),
('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z')
) as v(v)
order by checksum(##idle, ##cpu_busy, (select max(last_request_end_time) from sys.dm_exec_sessions where session_id=##spid), v.v)
for xml path('')
);
end
declare #randomnumber int = 1 + isnull(cast(session_context(N'randomnumber') as int), abs(checksum(#randomstring))%10000000);
select #combo6 = concat(
substring(#randomstring, p.p1, 1),
substring(#randomstring, p.p2, 1),
substring(#randomstring, p.p3, 1),
substring(#randomstring, p.p4, 1),
substring(#randomstring, p.p5, 1),
substring(#randomstring, p.p6, 1)
)
from
(
select
/*for 32 chars = len(rand32) */
(power(32,0)+(#randomnumber-1)%power(32, 1))/power(32,0) as p1,
(power(32,1)+(#randomnumber-1)%power(32, 2))/power(32,1) as p2,
(power(32,2)+(#randomnumber-1)%power(32, 3))/power(32,2) as p3,
(power(32,3)+(#randomnumber-1)%power(32, 4))/power(32,3) as p4,
(power(32,4)+(#randomnumber-1)%power(32, 5))/power(32,4) as p5,
(power(32,5)+(#randomnumber-1)%power(32, 6))/power(32,5) as p6
) as p;
exec sp_set_session_context #key=N'randomstring', #value=#randomstring;
exec sp_set_session_context #key=N'randomnumber', #value=#randomnumber;
return(#combo6);
end
go
exec sp_set_session_context #key=N'randomstring', #value=null;
exec sp_set_session_context #key=N'randomnumber', #value=null;
go
select top (100000) dbo.[why?]() as empid, a.name, b.object_id
from sys.all_objects as a
cross join sys.all_objects as b
go
--drop function dbo.[why?]
The difficulty reside on the UNIQUE feature of the string. Some solutions that have been shown cannot guarantee the uniqueness of the generated strings.
First solution of lptr does not give always 6 letters and give some duplicates.
My solution give the full requirement, but it is slow :
WITH TAZ AS
(SELECT CAST('A' COLLATE Latin1_General_BIN AS CHAR(1)) AS LETTER, ASCII('A') AS CAR
UNION ALL
SELECT CAST(CHAR(CAR + 1) COLLATE Latin1_General_BIN AS CHAR(1)), CAR + 1
FROM TAZ
WHERE CHAR(CAR + 1) <= 'Z'
)
SELECT TOP 4000000
T1.LETTER + T2.LETTER + T3.LETTER + T4.LETTER + T5.LETTER + T6.LETTER AS L6
FROM TAZ AS T1
CROSS JOIN TAZ AS T2
CROSS JOIN TAZ AS T3
CROSS JOIN TAZ AS T4
CROSS JOIN TAZ AS T5
CROSS JOIN TAZ AS T6
ORDER BY NEWID()
One thing I do in such a case is to compute all the 6 length strings possible and store it in a plain table stored in a compressed mode and in a read only storage (a tally table). The table has an ID and an extra column of bit type with the 0 value.
When you want to attribute some 6 chars string values, you just pickup from the table and marks it with the bit modify to 1.
As an information, this is the way that referenced ticket file are givent to customer in the french national railway compagny call SNCF since a long time.
First, let me start by saying you already have some fine answers here, the only problem is with them is that they are slow. My suggested solution is fast - even very fast in comparison.
A year ago I've written a blog post entitled How to pre-populate a random strings pool
that was based on an answer written by Martin Smith to How can I generate random strings in TSQL. I've basically took the code posted in that answer and wrapped it up inside a inline table valued function.
For this problem, I've taken that function and modified it ever so slightly to better fit your requirements - mainly the number of random strings (original version can produce up to 1,000,000 rows only) and the case-insensitivity.
Tests I've made comparing the speed of execution between Gordon's SQLpro's, lptr's answers and my own showed conclusively that this is the best solution between all four, at least in terms of execution speed.
So, without further ado, here's the code:
First, the function and it's auxiliary view:
-- This view is needed for the function to work. Read my blog post for details.
CREATE VIEW dbo.GuidGenerator
AS
SELECT Newid() As NewGuid;
GO
-- slightly modified version to enable the generation of up to 100,000,000 rows.
CREATE FUNCTION dbo.RandomStringGenerator
(
#Length int,
#Count int -- Note: up to 100,000,000 rows
)
RETURNS TABLE
AS
RETURN
WITH E1(N) AS (SELECT N FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) V(N)), -- 10
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --100
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10,000
Tally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY ##SPID) FROM E4 a, E4 b) -- 100,000,000
SELECT TOP(#Count)
N As Number,
(
SELECT TOP (#Length) CHAR(
CASE Abs(Checksum(NewGuid)) % 2
WHEN 0 THEN 65 + Abs(Checksum(NewGuid)) % 26 -- Random upper case letter
ELSE 48 + Abs(Checksum(NewGuid)) % 10 -- Random digit
END
)
FROM Tally As t0
CROSS JOIN GuidGenerator
WHERE t0.n <> -t1.n
FOR XML PATH('')
) As RandomString
FROM Tally As t1
GO
Then, using distinct, top 4000000 and a simple where clause - select the random strings you want:
SELECT DISTINCT TOP 4000000 Number, RandomString
FROM dbo.RandomStringGenerator(6,100000000)
WHERE RandomString NOT LIKE '%[IiOoLl01]%' -- in case your database's default collation is case sensitive...
The reason this is the fastest solution is very simple - My solution already generates the strings randomly, so I don't need to also sort them randomly - which is the biggest bottle neck of the other suggested solutions.
If you don't need the order to be random, you can go with SQLpro's solution, just remove the order by newid() - that was the fastest solution (though it didn't filter out the unwanted chars)
Update
As requested by lptr - here's an example on how to select the random strings and another table as well:
WITH Tbl AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Em_Id) As Rn
FROM <TableNameHere>
), Rnd AS
(
SELECT DISTINCT TOP 4000000 ROW_NUMBER() OVER (ORDER BY Number) As Rn, RandomString
FROM dbo.RandomStringGenerator(6,100000000)
WHERE RandomString NOT LIKE '%[IiOoLl01]%'
)
SELECT Em_Id, RandomString
FROM Tbl
INNER JOIN rnd
ON Tbl.Rn = Rnd.Rn
Notes:
Change <TableNameHere> to the actual table name
You can use any column (or constant) for the order by of the row number, it doesn't matter because the order is irrelevant here anyway.
I need to be able to apply unique 8 character strings per row on a table that has almost 2.5 million records.
I have tried this:
UPDATE MyTable
SET [UniqueID]=SUBSTRING(CONVERT(varchar(255), NEWID()), 1, 8)
Which works, but when I check the uniqueness of the ID's, I receive duplicates
SELECT [UniqueID], COUNT([UniqueID])
FROM NicoleW_CQ_2019_Audi_CR_Always_On_2019_T1_EM
GROUP BY [UniqueID]
HAVING COUNT([UniqueID]) > 1
I really would just like to update the table, as above, with just a simple line of code, if possible.
Here's a way that uses a temporary table to assure the uniqueness
Create and fill a #temporary table with unique random 8 character codes.
The SQL below uses a FOR XML trick to generate the codes in BASE62 : [A-Za-z0-9]
Examples : 8Phs7ZYl, ugCKtPqT, U9soG39q
A GUID only uses the characters [0-9A-F].
For 8 characters that can generate 16^8 = 4294967296 combinations.
While with BASE62 there are 62^8 = 2.183401056e014 combinations.
So the odds that a duplicate is generated are significantly lower with BASE62.
The temp table should have an equal of larger amount of records than the destination table.
This example only generates 100000 codes. But you get the idea.
IF OBJECT_ID('tempdb..#tmpRandoms') IS NOT NULL DROP TABLE #tmpRandoms;
CREATE TABLE #tmpRandoms (
ID INT PRIMARY KEY IDENTITY(1,1),
[UniqueID] varchar(8),
CONSTRAINT UC_tmpRandoms_UniqueID UNIQUE ([UniqueID])
);
WITH DIGITS AS
(
select n
from (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)
),
NUMS AS
(
select (d5.n*10000 + d4.n*1000 + d3.n*100 + d2.n * 10 + d1.n) as n
from DIGITS d1
cross join DIGITS d2
cross join DIGITS d3
cross join DIGITS d4
cross join DIGITS d5
)
INSERT INTO #tmpRandoms ([UniqueID])
SELECT DISTINCT LEFT(REPLACE(REPLACE((select CAST(NEWID() as varbinary(16)), n FOR XML PATH(''), BINARY BASE64),'+',''),'/',''), 8) AS [UniqueID]
FROM NUMS;
Then update your table with it
WITH CTE AS
(
SELECT ROW_NUMBER() OVER (ORDER BY ID) AS RN, [UniqueID]
FROM YourTable
)
UPDATE t
SET t.[UniqueID] = tmp.[UniqueID]
FROM CTE t
JOIN #tmpRandoms tmp ON tmp.ID = t.RN;
A test on rextester here
Can you just use numbers and assign a randomish value?
with toupdate as (
select t.*,
row_number() over (order by newid()) as random_enough
from mytable t
)
update toupdate
set UniqueID = right(concat('00000000', random_enough), 8);
See: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/a289ed64-2038-415e-9f5d-ae84e50fe702/generate-random-string-of-length-5-az09?forum=transactsql
Alter: DECLARE #s char(5) and SELECT TOP (5) c1 to fix length you want.
I am trying to pregenerate some alphanumeric strings and insert the result into a table. The length of string will be 5. Example: a5r67. Basically I want to generate some readable strings for customers so they can access their orders like
www.example.com/order/a5r67. Now I have a select statement:
;WITH
cte1 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
cte2 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
cte3 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
cte4 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t)),
cte5 AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t))
INSERT INTO ProductHandles(ID, Used)
SELECT cte1.t + cte2.t + cte3.t + cte4.t + cte5.t, 0
FROM cte1
CROSS JOIN cte2
CROSS JOIN cte3
CROSS JOIN cte4
CROSS JOIN cte5
Now the problem is I need to write something like this to get a value from the table:
SELECT TOP 1 ID
FROM ProductHandles
WHERE Used = 0
I will have index on the Used column so it will be fast. The problem with this is that it comes with order:
00000
00001
00002
...
I know that I can order by NEWID(), but that will be much slower. I know that there is no guarantee of ordering unless we specify Order By clause. What is needed is opposite. I need guaranteed chaos, but not by ordering by NEWID() each time customer creates order.
I am going to use it like:
WITH cte as (
SELECT TOP 1 * FROM ProductHandles WHERE Used = 0
--I don't want to order by newid() here as it will be slow
)
UPDATE cte
SET Used = 1
OUTPUT INSERTED.ID
If you add an identity column to the table, and use order by newid() when inserting the records (that will be slow but it's a one time thing that's being done offline from what I understand) then you can use order by on the identity column to select the records in the order they where inserted to the table.
From the Limitations and Restrictions part of the INSERT page in Microsoft Docs:
INSERT queries that use SELECT with ORDER BY to populate rows guarantees how identity values are computed but not the order in which the rows are inserted.
This means that by doing this you are effectively making the identity column ordered by the same random order the rows where selected in the insert...select statement.
Also, there is no need to repeat the same cte 5 times - you are already repeating the cross apply:
CREATE TABLE ProductHandles(sort int identity(1,1), ID char(5), used bit)
;WITH
cte AS(SELECT * FROM (VALUES('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('a'),('b'),('c'),('d'),('e'),('f'),('g'),('h'),('i'),('j'),('k'),('l'),('m'),('n'),('o'),('p'),('q'),('r'),('s'),('t'),('u'),('v'),('w'),('x'),('y'),('z')) AS v(t))
INSERT INTO ProductHandles(ID, Used)
SELECT a.t + b.t + c.t + d.t + e.t, 0
FROM cte a
CROSS JOIN cte b
CROSS JOIN cte c
CROSS JOIN cte d
CROSS JOIN cte e
ORDER BY NEWID()
Then the cte can have an order by clause that guarantees the same random order as the rows returned from the select statement populating this table:
WITH cte as (
SELECT TOP 1 *
FROM ProductHandles
WHERE Used = 0
ORDER BY sort
)
UPDATE cte
SET Used = 1
OUTPUT INSERTED.ID
You can see a live demo on rextester. (with only digits since it's taking too long otherwise)
Here's a slightly different option...
Rather than trying to generate all possible values in a single sitting, you could simply generate a million or two at a time and generate more as they get used up.
Using this approach, you drastically reduce the the initial creation time and eliminate the need to maintain the massive table of values, the majority of which, that will never be used.
CREATE TABLE dbo.ProductHandles (
rid INT NOT NULL
CONSTRAINT pk_ProductHandles
PRIMARY KEY CLUSTERED,
ID_Value CHAR(5) NOT NULL
CONSTRAINT uq_ProductHandles_IDValue
UNIQUE WITH (IGNORE_DUP_KEY = ON), -- prevents the insertion of duplicate values w/o generating any errors.
Used BIT NOT NULL
CONSTRAINT df_ProductHandles_Used
DEFAULT (0)
);
-- Create a filtered index to help facilitate fast searches
-- of unused values.
CREATE NONCLUSTERED INDEX ixf_ProductHandles_Used_rid
ON dbo.ProductHandles (Used, rid)
INCLUDE(ID_Value)
WHERE Used = 0;
--==========================================================
WHILE 1 = 1 -- The while loop will attempt to insert new rows, in 1M blocks, until required minimum of unused values are available.
BEGIN
IF (SELECT COUNT(*) FROM dbo.ProductHandles ph WHERE ph.Used = 0) > 1000000 -- the minimum num of unused ID's you want to keep on hand.
BEGIN
BREAK;
END;
ELSE
BEGIN
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
cte_Tally (n) AS (
SELECT TOP (1000000) -- Sets the "block size" of each insert attempt.
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n3 a CROSS JOIN cte_n3 b
)
INSERT dbo.ProductHandles (rid, ID_Value, Used)
SELECT
t.n + ISNULL((SELECT MAX(ph.rid) FROM dbo.ProductHandles ph), 0),
CONCAT(ISNULL(c1.char_1, n1.num_1), ISNULL(c2.char_2, n2.num_2), ISNULL(c3.char_3, n3.num_3), ISNULL(c4.char_4, n4.num_4), ISNULL(c5.char_5, n5.num_5)),
0
FROM
cte_Tally t
-- for each of the 5 positions, randomly generate numbers between 0 & 36.
-- 0-9 are left as numbers.
-- 10 - 36 are converted to lower cased letters.
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n1 (num_1)
CROSS APPLY ( VALUES (CHAR(CASE WHEN n1.num_1 > 9 THEN n1.num_1 + 87 END)) ) c1 (char_1)
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n2 (num_2)
CROSS APPLY ( VALUES (CHAR(CASE WHEN n2.num_2 > 9 THEN n2.num_2 + 87 END)) ) c2 (char_2)
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n3 (num_3)
CROSS APPLY ( VALUES (CHAR(CASE WHEN n3.num_3 > 9 THEN n3.num_3 + 87 END)) ) c3 (char_3)
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n4 (num_4)
CROSS APPLY ( VALUES (CHAR(CASE WHEN n4.num_4 > 9 THEN n4.num_4 + 87 END)) ) c4 (char_4)
CROSS APPLY ( VALUES (ABS(CHECKSUM(NEWID())) % 36) ) n5 (num_5)
CROSS APPLY ( VALUES (CHAR(CASE WHEN n5.num_5 > 9 THEN n5.num_5 + 87 END)) ) c5 (char_5);
END;
END;
After the initial creation, move the code in the WHILE loop to a stored procedure and schedule it to automatically run on a periodic basis.
If I'm understanding this right, It looks like your attempting to separate the URL/visible data from the DB record ID, as most apps use, and provide something that is not directly related to an ID field that the user will see. NEWID() does allow control of the number of characters so you could generate a smaller field with a smaller index. Or just use a portion of the full NEWID()
SELECT CONVERT(varchar(255), NEWID())
SELECT SUBSTRING(CONVERT(varchar(40), NEWID()),0,5)
You might also want to look at a checksum field, I don't know if its faster on indexing though. You could get crazier by combining random NEWID() with a checksum across 2 or 3 fields.
SELECT BINARY_CHECKSUM(5 ,'EP30461105',1)
I want to insert data in two columns(Alphabet, Number) in following format:
A,101
A,102
A,103
A,201
A,202
A,203
...
A,1203
...
B,101
B,102
B,103
....
Z,1203
Attempted solution:
I am able to achieve this by making two tables with one column each.
In first table I have kept A-Z, so 26 rows.
In second table I have kept numbers 101, 102, 103, 201, 202, 203...1203.
And then I used following query
Insert into testtable
select a.letter, n.ID
from Alphabet a
cross join Number n
select * from Alphabet
I achieved the required result, but I am not happy with this tedious approach.
Also, I attempted while loop, but failed so far.
Can you please help in finding and suggesting a better approach to achieve this requirement?
If you don't have a Numbers/Tally Table, you can use a an ad-hoc tally table.
Example
Declare #N1 int = 101
Declare #N2 int = 1203
Select C,N
From (Select Top (#N2-#N1+1) N=#N1-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1, master..spt_values n2 ) A
Cross Join (Select Top (26) C=char(65-1+Row_Number() Over (Order By (Select NULL))) From master..spt_values n1 ) B
Order by C,N
Returns 28,678 rows
C N
A 101
A 102
A 103
...
Z 1201
Z 1202
Z 1203
I have a small SQL based challenge that i'm trying to solve to better my knowledge of Dynamic SQL.
My requirements are as follows.
I created a table that looks as follows:
CREATE TABLE Prison_Doors
(
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
DoorOpen BIT,
DoorClosed BIT,
Trips INT
)
GO
I need to Create a Dynamic SQL Proc to insert 50 Door numbers and assign them as closed.
Expected result of proc:
|DoorNum|DoorOpen|DoorClosed|Trips|
|-------|--------|----------|-----|
| 1 | 0 | 1 |null |
|-------|--------|----------|-----|
|---------All the way to 50-------|
|-------|--------|----------|-----|
| 50 | 0 | 1 |null |
This is what I have written but it is not inserting:
BEGIN
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
VALUES('+CAST(#Index AS VARCHAR)+',0,1),'
SET #Index=#Index+1
END
SET #SQL = SUBSTRING(#SQL, 1, LEN(#SQL)-1)
EXEC(#SQL)
END
I would like to know what I am doing wrong.
after all of this is done I then need to run another loop to start at door one and change every second door to open and change trips to one and then increment to every 3 doors to open and trips becomes 2 and this incrementation continues until all doors are open which will then select the number of trips that it took.
I hope somebody can assist me with this as I am new to Dynamic SQL and I just need some guidance and not the complete solution.
Help is much appreciated :)
The error you got is because you are trying to insert into an identity column DoorNumber:
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
Remove that columns from the column list, instead of:
INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
remove that column DoorNum:
INSERT INTO Prison_Doors(DoorOpen,DoorClosed)
...
However, there is no need for dynamic SQL to do that, you can do this using an anchor table like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums
WHERE n <= 50;
Live Demo.
Update:
What my code does line by line?
Generating Sequence of numbers:
The first problem was generating a sequence of 50 numbers from 1 to 50, I used an anchor table with only four rows from 1 to 4 like this:
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n);
This syntax using the VALUES is new to SQL-Server-2008, it is called Row Value Constructor. After the VALUES, you assign an alias of the table and the target columns in parentheses like temp(n).
For old versions you have to use something like :
SELECT n
FROM
(
SELECT 1 AS n
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
) AS temp;
This will give you only 4 rows, but we need to generate 50. Thats why I CROSS JOIN this table three time with itself using:
FROM temp t1, temp t2, temp t3
It is the same as
FROM temp t1
CROSS JOIN temp t2
CROSS JOIN temp t3
This will multiply these rows 64 times, 4 rows3 = 64 rows:
1 1 1
1 2 1
1 3 1
1 4 1
2 1 1
2 2 1
2 3 1
2 4 1
....
3 1 4
3 2 4
3 3 4
3 4 4
4 1 4
4 2 4
4 3 4
4 4 4
The Use Of ROW_NUMBER() Function:
Then using the ROW_NUMBER() will give us a ranking number from 1 to 64 like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3;
Note that: ROW_NUMBER requires an ORDER BY clause, in or case it doesn't matter the order, so I used (SELECT 1).
There is also another way for generating a sequence numbers with out the use of ROW_NUMBER, it also depends on the CROSS JOIN, with an anchor table like:
WITH temp
AS
(
SELECT n
FROM (
VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)
) temp(n)
), nums
AS
(
SELECT t1.n * 100 + t2.n * 10 + t3.n + 1 AS n
FROM temp t1, temp t2, temp t3
)
SELECT n
FROM nums
ORDER BY n;
Another Way of Generating Sequence Of Numbers
Common Table Expressions:
The CTE is called common table expression, and it was introdeced in SQL Server 2005. It is one of the table expressions types that SQL Server supports. The other three are:
Derived tables,
Views, and
Inline table-valued functions.
It has a lot of important advantages. One of them is let you create a virtual tables that you can reuse them later, like what I did:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
...
Here I defined two CTE's temp then another one nums that ruse that temp so this OL, you can create multiple CTE's, just put a semicolon, then a new one with the AS clause and two ().
Insert into one table from another table using INSERT INTO ... SELECT ...
Now, we have a virtual table nums having rows from 1 to 64, we need to insert the rows from 1 to 50.
For this, you can use the INSERT INTO ... SELECT ....
Note that the columns in the INSERT clause are optional, but If you do so, you have to put a value for each row, if not you will got an error, for example if you have four columns and you put only three values in the VALUES clause or in the SELECT clause, then you will got an error. This is not valid for the idenetityt columns which are defined with:
Identity(1,1)
^ ^
| |
| ------------------The seed
The start
In this case you simply ignore that column in the columns list in the INSERT clause and it will have the identity value. There is an option that let you insert a value manually like in the #Raj's answer.
Note that: In my answer, I am not inserting the sequence numbers in to that column instead, inserting the values 50 times. But the actual numbers are generating automatically because of the Identity column:
...
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums;
WHERE n <= 50;
Firstly, you have declared DoorNum as Identity and then you are trying to explicitly insert values into that column. SQL Server does not allow this, unless you choose to
SET IDENTITY_INSERT ON
Try this query. It should insert the 50 rows you want
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorOpen,DoorClosed)VALUES(0,1)'
EXEC(#SQL)
SET #Index=#Index+1
END
Raj