Related
We have current table named Article:
id
name
1
artikel_a
2
artikel_b
3
artikel_c
id is a numeric(5, 0)
Its very important that similar articles have very similar IDs, so my client wants to see list of all the possible (currently unused) id numbers when he creates a new article record. That way they can look at a range that fits for current article creation.
How can I do this in SQL Server?
One possible solution
Declare #YourTable Table ([id] int,[name] varchar(50)) Insert Into #YourTable Values
(1,'aaa')
,(2,'bbb')
,(3,'ccc')
,(25,'ddd')
,(50,'eee')
Select R1 = min(N)
,R2 = max(N)
From (
Select N
,Grp = N-row_number() over (order by N)
From (
Select Top 99999 N=Row_Number() Over (Order By (Select NULL))
From master..spt_values n1, master..spt_values n2
) src
where not exists (Select 1 from #YourTable where N=id)
) A
Group By Grp
Results of Available IDs
R1 R2
4 24
26 49
51 99999
Note:
Subquery A will give you a long list of open ID's
Hi i am having Ranking table
CREATE TABLE [dbo].[Ranking](
[Code] [varchar](5) NULL,
[Name] [varchar](50) NULL,
[RankFrom] [varchar](5) NULL,
[RankTo] [varchar](5) NULL
) ON [PRIMARY]
Adding following Values
INSERT INTO [Ranking]([Code],[Name],[RankFrom],[RankTo])VALUES('01','Hisanth','105','110')
My result as follows when my query is
select * from Ranking
Code
Name
RankFrom
RankTo
01
Hisanth
105
110
But I need to get result like as follows
Code
Name
RankTo
01
Hisanth
105
01
Hisanth
106
01
Hisanth
107
01
Hisanth
108
01
Hisanth
109
01
Hisanth
110
How to write query for this
One option uses a recursive query:
with cte as (
select code, name, rankfrom, rankto from ranking
union all
select code, name, rankfrom, rankto - 1 from cte where rankto > rankfrom
)
select code, name, rankto from cte
If your ranges may be greater than 100, then you need to add option(maxrecursion 0) at the very end of the query.
If you really are storing these numbers as strings (which obviously is not a good idea), then you would need to convert them first:
with cte as (
select code, name, convert(int, rankfrom) as rankfrom, convert(int, rankto) as rankto from ranking
union all
select code, name, rankfrom, rankto - 1 from cte where rankto > rankfrom
)
select code, name, rankto from cte
The use of recusion is the worst thing to do in terms of performances. You never should write a CTE recursive query like the one that have been given in the first answer, but create a hard table of number into your database, like this one :
CREATE TABLE N (i INT PRIMARY KEY);
INSERT INTO N VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);
INSERT INTO N
SELECT N1.i + 10*N2.i + 100*N3.i + 1000*N4.i
FROM N AS N1
CROSS JOIN N AS N2
CROSS JOIN N AS N3
CROSS JOIN N AS N4
WHERE N1.i + 10*N2.i + 100*N3.i + 1000*N4.i >= 10;
Then the answer is trivial :
SELECT R.*
FROM Ranking AS R
JOIN N ON i BETWEEN R.RANKFROM AND R.RANKTO;
To execute it faster, create an index like this one :
CREATE INDEX X_RNK_FROM_TO ON Ranking (RANKFROM, RANKTO);
To have much more speed, store the N table in a readonly filegroup.
IN CONCLUSION : every database should have a number table and a calendar one. They are called tally table and are a great use for this typical cases.
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 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 have a table with one column containing different integers.
For each integer in the table I would like to duplicate it as the number of digits -
For example:
12345 (5 digits):
1. 12345
2. 12345
3. 12345
4. 12345
5. 12345
I thought doing it using with recursion t (...) as () but I didn't manage, since I don't really understand how it works and what is happening "behind the scenes.
I don't want to use insert because I want it to be scalable and automatic for as many integers as needed in a table.
Any thoughts and an explanation would be great.
The easiest way is to join to a table with numbers from 1 to n in it.
SELECT n, x
FROM yourtable
JOIN
(
SELECT day_of_calendar AS n
FROM sys_calendar.CALENDAR
WHERE n BETWEEN 1 AND 12 -- maximum number of digits
) AS dt
ON n <= CHAR_LENGTH(TRIM(ABS(x)))
In my example I abused TD's builtin calendar, but that's not a good choice, as the optimizer doesn't know how many rows will be returned and as the plan must be a Product Join it might decide to do something stupid. So better use a number table...
Create a numbers table that will contain the integers from 1 to the maximum number of digits that the numbers in your table will have (I went with 6):
create table numbers(num int)
insert numbers
select 1 union select 2 union select 3 union select 4 union select 5 union select 6
You already have your table (but here's what I was using to test):
create table your_table(num int)
insert your_table
select 12345 union select 678
Here's the query to get your results:
select ROW_NUMBER() over(partition by b.num order by b.num) row_num, b.num, LEN(cast(b.num as char)) num_digits
into #temp
from your_table b
cross join numbers n
select t.num
from #temp t
where t.row_num <= t.num_digits
I found a nice way to perform this action. Here goes:
with recursive t (num,num_as_char,char_n)
as
(
select num
,cast (num as varchar (100)) as num_as_char
,substr (num_as_char,1,1)
from numbers
union all
select num
,substr (t.num_as_char,2) as num_as_char2
,substr (num_as_char2,1,1)
from t
where char_length (num_as_char2) > 0
)
select *
from t
order by num,char_length (num_as_char) desc