SQL Select 'n' records without a Table - sql

Is there a way of selecting a specific number of rows without creating a table. e.g. if i use the following:
SELECT 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
It will give me 10 across, I want 10 New Rows.
Thanks

You can use a recursive CTE to generate an arbitrary sequence of numbers in T-SQL like so:
DECLARE #start INT = 1;
DECLARE #end INT = 10;
WITH numbers AS (
SELECT #start AS number
UNION ALL
SELECT number + 1
FROM numbers
WHERE number < #end
)
SELECT *
FROM numbers
OPTION (MAXRECURSION 0);

If you have a fixed number of rows, you can try:
SELECT 1
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

This is a good way if you need a long list (so you don't need lots of UNIONstatements:
WITH CTE_Numbers AS (
SELECT n = 1
UNION ALL
SELECT n + 1 FROM CTE_Numbers WHERE n < 10
)
SELECT n FROM CTE_Numbers

The Recursive CTE approach - is realy good.
Be just aware of performance difference. Let's play with a million of records:
Recursive CTE approach. Duration = 14 seconds
declare #start int = 1;
declare #end int = 999999;
with numbers as
(
select #start as number
union all
select number + 1 from numbers where number < #end
)
select * from numbers option(maxrecursion 0);
Union All + Cross Join approach. Duration = 6 seconds
with N(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
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Table Value Constructor + Cross Join approach. Duration = 6 seconds
(if SQL Server >= 2008)
with N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Recursive CTE + Cross Join approach. :) Duration = 6 seconds
with N(n) as
(
select 1
union all
select n + 1 from N where n < 10
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
We will get more amazing effect if we try to INSERT result into a table variable:
INSERT INTO with Recursive CTE approach. Duration = 17 seconds
declare #R table (Id int primary key clustered);
with numbers as
(
select 1 as number
union all
select number + 1 from numbers where number < 999999
)
insert into #R
select * from numbers option(maxrecursion 0);
INSERT INTO with Cross Join approach. Duration = 1 second
declare #C table (Id int primary key clustered);
with N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
)
insert into #C
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Here is an interesting article about Tally Tables

SELECT 1
UNION
SELECT 2
UNION
...
UNION
SELECT 10 ;

Using spt_values table:
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY number)
FROM [master]..spt_values ORDER BY n;
Or if the value needed is less than 1k:
SELECT DISTINCT n = number FROM master..[spt_values] WHERE number BETWEEN 1 AND 1000;
This is a table that is used by internal stored procedures for various purposes. Its use online seems to be quite prevalent, even though it is undocumented, unsupported, it may disappear one day, and because it only contains a finite, non-unique, and non-contiguous set of values. There are 2,164 unique and 2,508 total values in SQL Server 2008 R2; in 2012 there are 2,167 unique and 2,515 total. This includes duplicates, negative values, and even if using DISTINCT, plenty of gaps once you get beyond the number 2,048. So the workaround is to use ROW_NUMBER() to generate a contiguous sequence, starting at 1, based on the values in the table.
In addition, to aid more values than 2k records, you could join the table with itself, but in common cases, that table itself is enough.
Performance wise, it shouldn't be too bad (generating a million records, it took 10 seconds on my laptop), and the query is quite easy to read.
Source: http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1

Using PIVOT (for some cases it would be overkill)
DECLARE #Items TABLE(a int, b int, c int, d int, e int);
INSERT INTO #Items
VALUES(1, 2, 3, 4, 5)
SELECT Items
FROM #Items as p
UNPIVOT
(Items FOR Seq IN
([a], [b], [c], [d], [e]) ) AS unpvt

;WITH nums AS
(SELECT 1 AS value
UNION ALL
SELECT value + 1 AS value
FROM nums
WHERE nums.value <= 99)
SELECT *
FROM nums

Using GENERATE_SERIES - SQL Server 2022
Generates a series of numbers within a given interval. The interval and the step between series values are defined by the user.
SELECT value
FROM GENERATE_SERIES(START = 1, STOP = 10);

Related

Insert unique value multiple times depending on variable column

I have the following columns in my table:
PromotionID | NumberOfCodes
1 10
2 5
I need to insert the PromotionID into a new SQL table a number of times - depending on the value in NumberOfCodes.
So given the above, my result set should read:
PromotionID
1
1
1
1
1
1
1
1
1
1
2
2
2
2
2
You need recursive cte :
with r_cte as (
select t.PromotionID, 1 as start, NumberOfCodes
from table t
union all
select id, start + 1, NumberOfCodes
from r_cte
where start < NumberOfCodes
)
insert into table (PromotionID)
select PromotionID
from r_cte
order by PromotionID;
Default recursion level 100, use query hint option(maxrecursion 0) if you have NumberOfCodes more.
I prefer a tally for such things. Once you start getting to larger row sets, the performance of an rCTe degrades very quickly:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --Up to 100 rows. Add more cross joins to N for more rows
SELECT YT.PromotionID
FROM dbo.YourTable YT
JOIN Tally T ON YT.NumberOfCodes >= T.I;
One option uses a recursive common table expression to generate the rows from the source table, that you can then insert in the target table:
with cte as (
select promotion_id, number_of_codes n from sourcetable
union all select promotion_id, n - 1 from mytable where n > 1
)
insert into targettable (promotion_id)
select promotion_id from cte

Generate Random Test Data with ORDER BY NEWID() , include duplicate rows

I need to select random rows from a table for test data. There may be times I need more rows of test data than there are records in the table. Duplicates are okay. How do I structure my select so that I can get duplicate rows?
CREATE TABLE [Northwind].[dbo].[Persons]
(PersonID int, LastName varchar(255))
INSERT INTO [Northwind].[dbo].[Persons]
VALUES
(1, 'Smith'),
(2, 'Jones'),
(3, 'Washington')
SELECT TOP 5 *
FROM [Northwind].[dbo].[Persons]
ORDER BY NEWID()
How do I get the Select statement to give me five records in random order, with repeats? Currently, it only returns three in random order.
I'd like to be able to extend this to get 100 rows or 1000 rows or however many I need.
Use a recursive CTE to union enough rows so that they are larger than what you desire. Then select from that as you have done before.
declare
#desired int = 5,
#actual int = (select count(*) from persons);
with
persons as (
select personId,
lastName,
batch = 0
from Persons
union all
select personId,
lastName,
batch = batch + 1
from persons
where (batch + 1) * #actual < #desired
)
select
top (#desired) personId, lastName
from persons
order by newid()
As mentioned. You could instead us a tally table and then get the random rows;
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)))N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3, N N4) --Repeat for more
SELECT TOP 500 YT.*
FROM Tally T
CROSS JOIN YourTable YT
ORDER BY NEWID();
I was thinking of how you would solve this without ordering all the records, especially multiple times.
One method is to generate random numbers and to use those for looking up values in your data:
with n as (
select rand(checksum(newid())) as r, 1 as n
union all
select rand(checksum(newid())) as r, n + 1
from n
where n < 10
),
tt as (
select t.*, lag(tile_end, 1, 0) over (order by tile_end) as tile_start
from (select t.*, row_number() over (order by newid()) * 1.0 / count(*) over () as tile_end
from t
) t
)
select tt.*, n.r, (select count(*) from n)
from n left join
tt
on n.r >= tt.tile_start and n.r < tt.tile_end;
Here is a db<>fiddle. The row_number() does not need to use order by newid(). It can order by a key that has an index -- which makes that component much more efficient.
For more than 100 rows, you will need OPTION (MAXRECURSION 0).
I added a temp results table and looped through the query and pushed the results into the temp table.
declare #results table(
SSN varchar(10),
Cusip varchar(10),
...
EndBillingDate varchar(10))
DECLARE #cnt INT = 0;
WHILE #cnt < #trades
BEGIN
INSERT INTO #results
Select ...
set #cnt = #cnt + 10
END
select * from #results

How to find a number that isn't there?

We have a custom number field on a training record, the number is recorded sequentially but there are gaps. How do I find those gaps? Consider this pseudocode
SELECT MIN(X)
FROM DUAL
WHERE X BETWEEN 1 AND 999999
AND X NOT IN (SELECT AG_TRNID
FROM PS_TRAINING
WHERE AG_TRNID = X)
This doesn't work, "X" is unknown.
Thanks!
Bruce
This answer assumes that you're using Oracle.
The way to solve this is to create a resultset that contains all of the numbers in your range, then join to that. The way to do this is to use a recursive query:
SELECT LEVEL AS x
FROM DUAL
CONNECT BY LEVEL <= 999999
CONNECT BY is Oracle-specific syntax that tells the query to run recursively as long as the predicate is true. level is a pseudo-column that only exists in queries that use CONNECT BY that indicates the level of recursion. The end result is that this query will run the query against dual 999,999 times, each time being a level deeper in the recursion.
Given this method of generating numbers, plugging it into the query you tried earlier is pretty trivial:
SELECT MIN (x)
FROM (SELECT LEVEL AS x
FROM DUAL
CONNECT BY LEVEL <= 999999)
WHERE x NOT IN (SELECT ag_trnid
FROM ps_training
WHERE ag_trnid = x)
Two quick examples. The first is your classic Gaps-and-Islands, and the second will use an ad-hoc tally table to identify the missings elements via a LEFT JOIN.
The following was created in SQL Server, but if your database supports the window functions, it should be a small task to adapt.
Gaps and Islands
Declare #YourTable table (X int)
Insert Into #YourTable values
(1),(2),(3),(5),(6),(10)
Select X1 = min(X)
,X2 = max(X)
From (
Select *
,Grp = X - Row_Number() over (Order by X)
From #YourTable
) A
Group By Grp
Returns
X1 X2
1 3
5 6
10 10
Ad-hoc Tally Table
Declare #YourTable table (X int)
Insert Into #YourTable values
(1),(2),(3),(5),(6),(10)
;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cteN(N) As (Select Row_Number() over (Order By (Select NULL)) From cte0 N1, cte0 N2, cte0 N3, cte0 N4, cte0 N5, cte0 N6) -- 1 Million
Select N
From cteN
Left Join #YourTable on X=N
Where X is Null
and N<=(Select max(X) from #YourTable)
Order By N
Returns
N
4
7
8
9
Create all numbers from min(ag_trnid) to max(ag_trnid). From these remove the existing numbers:
with nums(num, maxnum) as
(
select min(ag_trnid) as num, max(ag_trnid) as maxnum from ps_training
union all
select num + 1, maxnum from nums where num < maxnum
)
select num from nums
minus
select ag_trnid from ps_training;
Start with 1 (or 0 for that matter) instead of min(ag_trnid), if you consider numbers before the minimum ag_trnid gaps, too.
I think there's no other way besides create a table's id to find the gaps
declare #idMin bigint
declare #idMax bigint
set #idMin = (select min(AG_TRNID) from PS_TRAINING)
set #idMax = (select max(AG_TRNID) from PS_TRAINING)
create table numbers
(id bigint)
while (#idMax>#idMin)
begin
insert into numbers values(#idMin)
set #idMin=#idMin+1
end
select id from numbers
where id not in (SELECT AG_TRNID FROM PS_TRAINING)
You can create a "virtual" table that contains all numbers from 1 to 999999 then remove those that are present in your table:
select level
from dual
connect by level <= 999999
minus
select ag_trnid
from ps_training;
The above will output all gaps, not just the first one.
The connect by level <= 999999 is an undocumented trick to generate all numbers from 1 to 999999.
Here's one way of finding the start and end numbers of the gaps:
WITH sample_data AS (SELECT 1 ID FROM dual UNION ALL
SELECT 2 ID FROM dual UNION ALL
SELECT 4 ID FROM dual UNION ALL
SELECT 5 ID FROM dual UNION ALL
SELECT 8 ID FROM dual UNION ALL
SELECT 10 ID FROM dual UNION ALL
SELECT 20 ID FROM dual UNION ALL
SELECT 22 ID FROM dual UNION ALL
SELECT 23 ID FROM dual UNION ALL
SELECT 27 ID FROM dual UNION ALL
SELECT 28 ID FROM dual)
-- end of mimicking data in a table called "sample_data"
-- see SQL below:
SELECT prev_id + 1 first_number_in_gap,
ID - 1 last_number_in_gap
FROM (SELECT ID,
LAG(ID, 1, ID - 1) OVER (ORDER BY ID) prev_id
FROM sample_data)
WHERE ID - prev_id > 1;
FIRST_NUMBER_IN_GAP LAST_NUMBER_IN_GAP
------------------- ------------------
3 3
6 7
9 9
11 19
21 21
24 26
In the end I created a table with all possible values and select the minimum from it that doesn't exist in the base table. It works well. I had hoped for a sql statement to avoid creating yet another object but couldn't.
Yes this is Oracle, I neglected to mention.
Thank you all for the suggestions and assistance, it is much appreciated!

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

how to count each characters in a string and display each instance in a table format

my table master_schedule
cable_no
D110772
D110773
D110774
D110775
D110776
D110777
D110778
D110779
D110880
I would like to create a loop so that each character in the string counted and displayed
D 9
1 18
2 1
3 1
AND SO ON .......
how can i modify in these sql query mentioned below:
select (sum(LEN(cable_no) - LEN(REPLACE(cable_no, 'D', '')))*2) as FERRUL_qtyx2
from MASTER_schedule
Something like this:
select substring(cable_no, n.n, 1) as letter, count(*) as cnt
from FERRUL_qtyx2 t cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
group by substring(cable_no, n.n, 1);
This creates a sequence of numbers n up to the length of the string. It then uses cross join and substring() to extract the nth character of each cable_no.
In general, this will be faster than doing a union all seven times. The union all approach will typically scan the table 7 times. This will scan the table only once.
you can use recursive common table expression:
with cte(symbol, cable_no) as (
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from Table1
union all
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from cte
where cable_no <> ''
)
select symbol, count(*)
from cte
group by symbol
=> sql fiddle demo
Another approach (made after Gordon Linoff solution):
;with cte(n) as (
select 1
union all
select n + 1 from cte where n < 7
)
select substring(t.cable_no, n.n, 1) as letter, count(*) as cnt
from #Table1 as t
cross join cte as n
group by substring(t.cable_no, n.n, 1);
=> sql fiddle demo