Create a Range From n to 1 in SQL - sql

I need to create a range number from 1 to n.
For example, the parameter is #StartingValue
#StartingValue int = 96
Then the result should be:
Number
-------------
96
95
94
93
92
ff.
1
Does anyone have an idea how to do this?
Thank you.

Use a Tally Table to generate the numbers:
DECLARE #N INT = 96
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally(N) AS(
SELECT TOP(#N) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E8
)
SELECT * FROM CteTally ORDER BY N DESC
Explanation taken from Jeff's article (linked above):
The CTE called E1 (as in 10E1 for scientific notation) is nothing more
than ten SELECT 1's returned as a single result set.
E2 does a CROSS JOIN of E1 with itself. That returns a single result
set of 10*10 or up to 100 rows. I say "up to" because if the TOP
function is 100 or less, the CTE's are "smart" enough to know that it
doesn't actually need to go any further and E4 and E8 won't even come
into play. If the TOP has a value of less than 100, not all 100 rows
that E2 is capable of making will be made. It'll always make just
enough according to the TOP function.
You can follow from there. E4 is a CROSS JOIN of E2 and will make up
to 100*100 or 10,000 rows and E8 is a CROSS JOIN of E4 which will make
more rows than most people will ever need. If you do need more, then
just add an E16 as a CROSS JOIN of E8 and change the final FROM clause
to FROM E16.
What's really amazing about this bad-boy is that is produces ZERO
READS. Absolutely none, nada, nil.

One simple method is a numbers table. For a reasonable number (up to the low thousands), you can use spt_values:
with numbers as (
select top 96 row_number() over (order by (select null)) as n
from t
)
. . .
Another method is a recursive CTE:
with numbers as (
select 96 as n
union all
select n - 1
from numbers
where num > 1
)
For larger values, you'll need to use the MAXRECURSION option.

And another way.
SELECT N.number FROM
master..spt_values N
WHERE
N.type = 'P' AND
N.number BETWEEN 1 AND 96
ORDER BY N.number DESC
More details on spt_values What is the purpose of system table master..spt_values and what are the meanings of its values?

Sequance of no's can be generated by following ways:
1. Using row_number by querying a large table and get the sequance.
2. Using system tables as you can see other people comments.
3. Using recursive CTE.
declare #maxValue int = 96
; WITH rangetest AS
(
SELECT MinValue = #maxValue
UNION ALL
SELECT MinValue = MinValue - 1
FROM rangetest
WHERE MinValue > 1
)
SELECT *
from rangetest
OPTION (MAXRECURSION 0)

Related

Scalar subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values

I'm trying to write a query in BigQuery that select some rows based on row number and sum the columns.
In details, I have two tables: in table1 there are for each id the number of rows of table2 that I want to sum. An example of the two tables is below.
table1
table2
The desired output is:
id
points1
points2
points3
a
86
99
31
b
91
59
15
c
122
183
118
I created a UDF that tooks the 'neighbors' n1, n2 and n3 and sum the rows of table2 whose row_num is in n1, n2 and n3; then I recalled the UDF in my query below.
create temp function sum_points(neighbors array<int>)
returns array<int>
as (sum((select * from `project.dataset.table2` where row_num in unnest(neighbors))));
with cte as (
select id, array_agg(nn) as neighbors
from `project.dataset.table1`, unnest([n1, n2, n3]) nn
group by id
)
select id, sum_points(neighbors) from cte
However, I got the following error:
Scalar subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values; failed to parse CREATE [TEMP] FUNCTION statement at [5:9]
and it is not very clear to me what that means. I tried to replace the select inside with statement with select struct<array<int>> but it did not work.
Better option would be to join tables and do aggregate.
You can join tables based on row_num from table2 and n1, n2, n3 from table1 as below.
SELECT
id,
SUM(points1) AS points1,
SUM(points2) AS points2,
SUM(points3) AS points3
FROM table1 JOIN table2
ON row_num in (n1, n2, n3)
GROUP BY id
Output of the query:
id
points1
points2
points3
a
86
99
31
c
122
183
118
b
91
59
15

Trying to generate a range of numbers excluding certain numbers (with outer query identifier)

There have been quite a number of question regarding ranges in sql however i cant find anything that resembles my use case (most refer to a Tally or numbers table or procedures, something im trying to avoid seeing the elegance in the link below which uses neither)
Im trying to generate numbers between 1 and x, x coming for another table whilst excluding certain numbers from yet another table. (although there is a link between the 2, see the join)
Based on the answer for generating a range of numbers located at https://stackoverflow.com/a/64151448/1161646, im trying to do the following (approximation of the problem not the actual query):
select number
from excluding_numbers as exclusions join ranges range on range.id = exclusions.ranges_id,
(Select 0 + ROW_NUMBER() over (order by (Select null)) as number
from string_split(replicate(' ', range.limit - 1), ' ')) as numbers
where exclusions.number != number
and range.id = 1
The problem is the range.limit in the inner query 'numbers'
Example data:
ranges
id
limit
1
120000
2
10
3
10000000000
excluding_numbers
id
number
ranges_id
1
1
2
2
2
2
3
50000
3
This is saying from the range of 1 to 10 (ranges id 2) exclude number 1 and 2, from ranges 1 to 10000000000 (ranges id 3) only exclude number 50000.
Assuming I understand correctly, and that your numbers are 1+, you could use a Tally table to do this. An inline method would be one method:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT x FROM dbo.YourTable) --Table with the number of rows you want. I assume this has one row
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3, N N4, N N5, N N6, N N7) --Up to 10,000,000 rows
SELECT T.I
FROM Tally T
WHERE NOT EXISTS (SELECT 1
FROM dbo.ExcludeTable ET--Table with numbers to exclude
WHERE ET.Number = T.I);
You could, however, create an inline table function for your tally, and then reference that instead:
CREATE FUNCTION [dbo].[Tally] (#LastNumber bigint, #Zero bit)
RETURNS table
AS RETURN
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT 0 AS I
WHERE #Zero = 0
AND #LastNumber IS NOT NULL --Return 0 rows if NULL
UNION ALL
SELECT TOP (ISNULL(#LastNumber,0)) --Return 0 rows if NULL
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3, N N4, N N5, N N6, N N7) --Up to 10,000,000 rows
SELECT I
FROM Tally T;
GO
SELECT T.I
FROM dbo.YourTable YT --Table with the number of rows you want. I assume this has one row
CROSS APPLY dbo.Tally(YT.x,1) T
WHERE NOT EXISTS (SELECT 1
FROM dbo.ExcludeTable ET--Table with numbers to exclude
WHERE ET.Number = T.I);

how to get max of values by row and colmun in sql

Table has values
10 20 30
40 50 60
70 80 90
we need to show data maximum of row-wise and column-wise.
Result should be
RowWiseMax, ColumnWiseMax
30, 70
60, 80
90, 90
Here's a solution to the puzzle as presented, I've had to make the assumption that the number of columns is not dynamic, this solution works using T-Sql, it's not clear what your database platform is but should hopefully port if different. Using a table named "T" with columns c1, c2, c3
select RowWiseMax,
case Row_Number() over (order by RowWiseMax)
when 1 then Max(c1) over()
when 2 then Max(c2) over()
when 3 then Max(c3) over()
end ColumnWiseMax
from T
cross apply (
select Max(v) RowWiseMax
from (values (c1), (c2), (c3))v(v)
)x

Populate a sql table with duplicate data except for one column

I have a sql table :
Levels
LevelId Min Product
1 x 1
2 y 1
3 z 1
4 a 1
I need to duplicate the same data into the database by changing only the product Id from 1 2,3.... 40
example
LevelId Min Product
1 x 2
2 y 2
3 z 2
4 a 2
I could do something like
INSERT INTO dbo.Levels SELECT top 4 * fROM dbo.Levels
but that would just copy paste the data.
Is there a way I can copy the data and paste it changing only the Product value?
You're most of the way there - you just need to take one more logical step:
INSERT INTO dbo.Levels (LevelID, Min, Product)
SELECT LevelID, Min, 2 FROM dbo.Levels WHERE Product = 1
...will duplicate your rows with a different product ID.
Also consider that WHERE Product = 1 is going to be more reliable than TOP 4. Once you have more than four rows in the table, you will not be able to guarantee that TOP 4 will return the same four rows unless you also add an ORDER BY to the select, however WHERE Product = ... will always return the same rows, and will continue to work even if you add an extra row with a product ID of 1 (where as you'd have to consider changing TOP 4 to TOP 5, and so on if extra rows are added).
You can generate the product id's and then load them in:
with cte as (
select 2 as n
union all
select n + 1
from cte
where n < 40
)
INSERT INTO dbo.Levels(`min`, product)
SELECT `min`, cte.n as product
fROM dbo.Levels l cross join
cte
where l.productId = 1;
This assumes that the LevelId is an identity column, that auto-increments on insert. If not:
with cte as (
select 2 as n
union all
select n + 1
from cte
where n < 40
)
INSERT INTO dbo.Levels(levelid, `min`, product)
SELECT l.levelid+(cte.n-1)*4, `min`, cte.n as product
fROM dbo.Levels l cross join
cte
where l.productId = 1;
INSERT INTO dbo.Levels (LevelId, Min, Product)
SELECT TOP 4
LevelId,
Min,
2
FROM dbo.Levels
You can include expressions in the SELECT statement, either hard-coded values or something like Product + 1 or anything else.
I expect you probably wouldn't want to insert the LevelId though, but left that there to match your sample. If you don't want that just remove it from the INSERT and SELECT sections.
You could use a CROSS JOIN against a numbers table, for example.
WITH
L0 AS(SELECT 1 AS C UNION ALL SELECT 1 AS O), -- 2 rows
L1 AS(SELECT 1 AS C FROM L0 AS A CROSS JOIN L0 AS B), -- 4 rows
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS N FROM L1)
SELECT
lvl.[LevelID],
lvl.[Min],
num.[N]
FROM dbo.[Levels] lvl
CROSS JOIN Nums num
This would duplicate 4 times.

SQL Query to fetch numbers in given steps between a range

I have a set of data like this:
MinNo: 2500
MaxNo: 2700
IncrementStep: 10
Between the minimum number and the maximum number a list of all possible numbers with the given step are to be listed as shown below:
2500
2510
2520
2530
2540
2550
2560
2570
2580
2590
2600
2610
2620
2630
2640
2650
2660
2670
2680
2690
2700
I'm aware that this can be achieved using a while loop. Kindly let me know if this can be done using a select query using Common Table Expressions (if needed). Thanks in advance.
You can use a numbers table (or master..spt_values).
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 2500
set #MaxNo = 2700
set #IncrementStep = 10
select #MinNo + Number * #IncrementStep
from master..spt_values
where type = 'P' and
number between 0 and (#MaxNo - #MinNo) / #IncrementStep
Or a recursive CTE
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num
from C
See this useful function
CREATE FUNCTION [dbo].[Sequence](#min INT, #max INT, #step INT)
RETURNS #ret TABLE (id INT PRIMARY KEY)
AS
BEGIN
WITH numbers(id) as
(
SELECT #min id
UNION ALL
SELECT id+#step
FROM numbers
WHERE id < #max
)
INSERT #ret
SELECT id FROM Numbers
OPTION(MAXRECURSION 0)
RETURN
END
#Mikael Eriksson already mentioned a numbers table / tally table (search for it online, there are LOTS of possible uses, many DBAs always want a tally table to be present in any system they manage)
I just wanted to share one non-recursive CTE-based "tally table" solution that I saw online the other day, that I think it amazingly elegant for its huge range (4 thousand million logical rows) and general applicability without any database dependencies:
WITH
E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT *
FROM cteTally
WHERE N >= 2500
AND N <= 2700
AND N % 10 = 0
I found it here, but I don't know whether that's the original source of this CTE.
The nice thing about it is that you don't need to worry about min, max, or step size, and yet it performs very well in most (one-off) situations. That said, it should NOT be used in any frequently-called business process; Any physical indexed numbers table will always perform better!
EDIT: I just searched a little more for the source of this method (I had missed the stackoverflow link in the article I referenced), and apparently it's originally attributed to Itzik Ben-Gan, from the bottom of page 255 in a book titled "Inside Microsoft SQL Server 2005 - T-SQL Querying" (says Jeff Moden, who I implicitly trust).