SQL with numbers - sql

I am trying to build a loop with SQL Server.
When I execute SELECT 1 UNION SELECT 2 UNION SELECT 3; SQL Server creates a table with 1 column and 4 rows.
Can anyone think of a way to achive the same however without using UNION?

You can use the values() table constructor:
select *
from (values (1), (2), (3), (4)) v(n);

You can do this easily with a recursive cte.
declare #max int=4
;with cte as
(
select 1 as val
union all
select val+1
from cte
where val < #max
)
select *
from cte

Here's a few common options to do this in case your use case expands hundreds or thousands of rows...
Commonly seen a a tally table...
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Or, using a function from Jeff Moden
CREATE FUNCTION [dbo].[fnTallyProg]
/**********************************************************************************************************************
Purpose:
Given a start value, end value, and increment, create a sequencial list of integers.
Programmers Notes:
1. The increment can be negative if the start value is greater than the end value. In other words, it can count down
as well as up.
Revison History:
Rev 00 - 18 Feb 2017 - Jeff Moden
- Rewrite original to take start, end, and increment parameters.
**********************************************************************************************************************/
(
#pStart BIGINT
,#pEnd BIGINT
,#pIncrement BIGINT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN WITH
E01(N) AS (SELECT NULL FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))E0(N)) --10 rows
,E04(N) AS (SELECT NULL FROM E01 a CROSS JOIN E01 b CROSS JOIN E01 c CROSS JOIN E01 d) --10 Thousand rows
,E16(N) AS (SELECT NULL FROM E04 a CROSS JOIN E04 b CROSS JOIN E04 c CROSS JOIN E04 d) --10 Quadrillion rows, which is crazy
,Tally(N) AS (SELECT TOP (ABS((#pEnd-#pStart+#pIncrement)/#pIncrement))
N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM E16
WHERE (#pStart<=#pEnd AND #pIncrement > 0)
OR (#pStart>=#pEnd AND #pIncrement < 0)
ORDER BY N
)
SELECT TOP (ABS((#pEnd-#pStart+#pIncrement)/#pIncrement))
N = (t.N-1)*#pIncrement+#pStart
FROM Tally t
ORDER BY t.N
;
GO
Or, another quick function by JL
CREATE FUNCTION [dbo].[tfn_Tally]
(
#NumOfRows BIGINT = 1000000
,#StartWith BIGINT = 1563984
)
/* ============================================================================
07/20/2017 JL, Created. Capable of creating a sequense of rows
ranging from -10,000,000,000,000,000 to 10,000,000,000,000,000
============================================================================ */
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)), -- 10 rows
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b), -- 100 rows
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b), -- 10,000 rows
cte_n4 (n) AS (SELECT 1 FROM cte_n3 a CROSS JOIN cte_n3 b), -- 100,000,000 rows
cte_Tally (n) AS (
SELECT TOP (#NumOfRows)
(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) + #StartWith
FROM
cte_n4 a CROSS JOIN cte_n4 b -- 10,000,000,000,000,000 rows
)
SELECT
t.n
FROM
cte_Tally t;

Just for fun, you can use an ad-hoc tally table
Example
Declare #N int =4
Select Top (#N) N=Row_Number() Over (Order By (Select NULL))
From master..spt_values n1
Returns
N
1
2
3
4

If all you need is a sequence of values to be joined to another table later on, I would strongly suggest to create a one time auxiliary table of numbers as described here
The code to generate such table is pretty basic:
SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(n)
That will created a persisted "Numbers" table in your db that then you can use to join to other tables or use from functions.

Related

Finding missing numbers in a table

I'd like to generate a query that will produce a list of index values that are missing from a SQL table. So far, what I have is
SELECT index - 1
FROM table
WHERE index - 1 NOT IN (
SELECT DISTINCT index
FROM table)
AND
index != 1;
This only finds the first missing index of a sequence of missing indices. I'd like to produce a query which outputs every number between 1 and MAX(index) which does not appear as an index in the table. Is there a way to do that?
You need a numbers table to find the missing index values
Recursive CTE approach
DECLARE #max_num INT = (SELECT Max(index)
FROM table);
WITH cte
AS (SELECT 1 AS num
UNION ALL
SELECT num + 1
FROM cte
WHERE num < #max_num)
SELECT *
FROM cte c
WHERE NOT EXISTS (SELECT 1
FROM table t
WHERE c.num = t.index)
ORDER BY c.num
OPTION (maxrecursion 0)
Another approach using tally table to generate numbers.
;WITH E1(N)
AS (SELECT *
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) tc (N)), --10E+1 or 10 rows
E2(N)
AS (SELECT 1 FROM E1 a CROSS JOIN E1 b), --10E+2 or 100 rows
E4(N)
AS (SELECT 1 FROM E2 a CROSS JOIN E2 b), --10E+4 or 10,000 rows max
--E5(N) AS (SELECT 1 FROM E4 a CROSS JOIN E1 b), -- 10000 * 10 = 100000
cte(NUM)
AS (SELECT ItemNumber = Row_number()OVER(ORDER BY N) FROM E4)
SELECT *
FROM cte c
WHERE NOT EXISTS (SELECT 1
FROM table t
WHERE c.num = t.index)
ORDER BY c.num
Currently it generates 10000 sequential row numbers an if your max value is more than 10000 then you add another cross join to increase the numbers
My preference for this type of query is to show ranges. A simple way uses lead():
select (index + 1) as first_missing, (next_index - 1) as last_missing,
(next_index - index - 1) as num_missing
from (select t.*, lead(index) over (order by index) as next_index
from t
) t
where next_index <> index + 1;
This shows every "internal" missing range. Because it does not use a table of numbers, it works efficiently regardless of the size of the table or the number of missing values.
It does not show "missing" values at the beginning and end of the table. Your question does not specify that as an issue.

Returning while-loop values as multiple rows in SQL Server instead of multiple result sets

I have the following T-SQL, used to generate some random values:
DECLARE #cnt INT = 0;
WHILE #cnt < 100
BEGIN
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
SET #cnt = #cnt + 1;
END;
This works well enough, except every string is returned as, what looks like, an independent result set.
Is there a way to refactor that query to return every value as one row in the same result set?
Environment is MS SQL Server 2008.
Thanks!
The best way to do this kind of thing is to forget about looping in t-sql. Using a numbers or tally table is a much better way to go about this.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
cross join cteTally t
where t.N < = 100
Here's a way to do it using a tally table:
;With Tally (N) As
(
Select 0 Union All
Select 1 Union All
Select 2 Union All
Select 3 Union All
Select 4 Union All
Select 5 Union All
Select 6 Union All
Select 7 Union All
Select 8 Union All
Select 9
), Numbers (N) As
(
Select Row_Number() Over (Order By A.N) Num
From Tally A -- 10
Cross Join Tally B -- 100
Cross Join Tally C -- 1000
Cross Join Tally D -- 10000
Cross Join Tally E -- 100000
), LookupString (X) As
(
Select '0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+'
)
Select Random_String = substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
From LookupString
Cross Join Numbers
Where N <= 100

SQL Server SQL Get list of unused ids

I have a table that ranges from 1-100000 but there are gaps in the ids where items have been deleted. I want a SQL statement that will return me a list of all unused ids in the table so I can get a list of items that were deleted.
I want the list but randomizing the list is a bonus actually. I think it can be done with a rand function...
I'd like to keep it ansi SQL if possible to maintain portability but if not, then that's ok...
You can do this with a use of Tally Table.
Create our sample data.
CREATE TABLE #ids(
id INT IDENTITY(1, 1)
)
SET IDENTITY_INSERT #ids ON
--Insert 100,000 rows
INSERT INTO #ids(id)
SELECT TOP 100000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM sys.columns a
CROSS JOIN sys.columns b
SET IDENTITY_INSERT #ids OFF;
-- Randomly delete 1000 rows
WITH cte AS(
SELECT TOP 1000 id
FROM #ids
ORDER BY NEWID()
)
DELETE FROM cte
Using a Tally Table, create a list of numbers from 1 - 100,000. Then use NOT EXISTS to get the unused ids. To randomize the list, add on ORDER BY NEWID() clause.
DECLARE #min INT = 1,
#max INT = 100000
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b),
Tally(N) AS(
SELECT TOP(#max) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM E8
)
SELECT N
FROM Tally t
WHERE NOT EXISTS(
SELECT 1 FROM #ids WHERE id = t.N
)
ORDER BY NEWID() -- Sorts the result in a random order

SQL to get sequence of phone numbers

I have table called PhoneNumbers with columns Phone and Range as below
here in the phone column i have a phone numbers and in range column i have a range of values i need the phone numbers to be included.For the first phone number 9125678463 I need to include the phone numbers till the range 9125678465 ie (9125678463,9125678464,9125678465).Similarly for other phone numbers too.here is the sample destination table should look like
How can i write the sql to get this?
Thanks in advance
I have a solution which goes a classic way BUT: it does not need recursions and it does not need any loops! And it works even if your range has length of 3 or 5, or whatever...
first i create a table with numbers (from 1 to 1 million in this example - you can adopt this in TOP () clause):
SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX idx_numbers ON dbo.Numbers(n)
;
if you have that table it's pretty simple:
;WITH phonenumbers
AS
(
SELECT phone,
[range],
CAST(RIGHT(phone,LEN([range])) AS INT) AS number_to_increase,
CAST(LEFT(phone,LEN(phone)-LEN([range])) + REPLICATE('0',LEN([range])) AS BIGINT) AS base_number
FROM PhoneNumbers
)
SELECT p.base_number + num.n
FROM phonenumbers p
INNER JOIN dbo.Numbers num ON num.n BETWEEN p.number_to_increase AND p.[range]
You don't have to use a CTE like here - it's just to see a bit clearer what the idea behind this approach is. Maybe this suits for you
You can use CTE like this:
;WITH CTE (PhoneNumbers, [Range], i) AS (
SELECT CAST(Phone AS bigint), [Range], CAST(1 AS bigint)
FROM yourTable
UNION ALL
SELECT CAST(PhoneNumbers + 1 AS bigint), [Range], i + 1
FROM CTE
WHERE (PhoneNumbers + 1) % 10000 <= [Range]
)
SELECT PhoneNumbers
FROM CTE
ORDER BY PhoneNumbers
Here is one example of using a tally table. In my system I have that set of ctes as a view so I never have to write it again.
if OBJECT_ID('tempdb..#PhoneNumbers') is not null
drop table #PhoneNumbers;
create table #PhoneNumbers
(
Phone char(10)
, Range smallint
)
insert #PhoneNumbers
select 9135678463, 8465 union all
select 3279275678, 5679 union all
select 6372938103, 8105;
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select *
from #PhoneNumbers p
join cteTally t on t.N >= RIGHT(Phone, 4) and t.N <= Range
order by p.Phone
One more approach:
--Creating dummy table
select '9999991234' phone, '1237' rang into #tbl
union
select '9999995689', '5692'
SELECT [phone] low
,(CAST(9999995689/10000 AS bigINT) * 10000 + [Rang]) high
into #tbl1
FROM #tbl
--Creating 'numbrs' to have numbers between 0 & 9999 i.e. max range
select (rn-1)rn
into #numbrs
from
(select row_number() over (partition by null order by A.object_id) rn from sys.objects A
cross join sys.objects B)A
where rn between 0 and 9999
select (low + rn)phn from #numbrs cross join #tbl1
where (low + rn) between low and high

How to populate a table with 5 million rows in MS SQL Server?

I'm working on an app that should find 26-letter code in char(26) column out of 5,760,000 rows. I need to know how long it's going to take. I'm using MS SQL Server 2012 Express.
I have a database which has only one table, myTable:
Idcolumn integer
CodeColumn char(26)
DateAndTimeColumn datetime
Column 'CodeColumn' has an index.
IdColumn is simply integer ID.
CodeColumn has "00592098715648275649283746" format (this is an example).
DateAndTimeColumn is a timestamp.
I would like to populate this table with data to do some tests and to find out how long it is going to take to get an answer from the database. I don't know how to write proper tsql statement to populate my table with 5,760,000 rows. Especially that second column is very long. How can I populate the table to get my table populated?
Let's say the data should be like this when I use statement
SELECT IdColumn, CodeColumn, DateAndTimeColumn FROM myTable;
Output:
1 00000000000000000000000001 2014-11-19 15:46:50.843
2 00000000000000000000000002 2014-11-19 15:46:54.310
3 00000000000000000000000003 2014-11-19 15:46:56.060
and so on ... till 5,760,000 rows.
How can I do that?
;WITH Numbers AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
CROSS JOIN sys.all_objects AS s3
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Numbers;
Here is another way to do this using Lamak's excellent example. The only difference is this will create a 10 million row cte with zero reads. When you use sys.all_objects it can get extremely slow because of all the I/O.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E6(N) AS (SELECT 1 from E4 a, E2 b, E1 c),
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E6
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM cteTally
where cteTally.N <= 5760000
Very similar to Lamak answer, this will not depends on your database structure:
;WITH Numbers AS
(
SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION
SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION
SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10
),
Joins AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[id]))
FROM Numbers AS s1 --10
CROSS JOIN Numbers AS s2 --100
CROSS JOIN Numbers AS s3 --1.000
CROSS JOIN Numbers AS s4 --10.000
CROSS JOIN Numbers AS s5 --100.000
CROSS JOIN Numbers AS s6 --1.000.000
CROSS JOIN Numbers AS s7 --10.000.000
)
INSERT INTO #YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Joins;
It just generate a list of numbers from 1 to 10 and then it crossjoins all the way to 10^7.