TSQL - Insert rows based off range of integers - sql

I am creating a SPROC that will take a number as a parameter. I need to take this number and get 2 numbers before it and 2 numbers after it and then insert those into a temp table. For example - The int is 355. I need to write an insert statemenet that inserts 353, 354, 355, 356 and 357. (-2 & +2). I am not entirely sure how to do this. I was thinking maybe a cursor? In C# I would do a for loop but I am not sure the right way to approach in a set language. Thanks!
Here is what I have so far:
CREATE PROCEDURE [dbo].[GetLanePrediction]
#startzip int
AS
BEGIN
--SET NOCOUNT ON;
DECLARE #posnegval int = 2
DECLARE #TempZips TABLE (ID INT IDENTITY(1,1), Zip INT)
--INSERT INTO #TempZips (Zip)
--Some kind of for loop or cursor here?
END
The output would create a temp table (#TempZips) with 5 rows. Like so:
ID Zip
1 353
2 354
3 355
4 356
5 357

Here is one simple method based on the question:
CREATE PROCEDURE [dbo].[GetLanePrediction] (
#onenumber int
) AS
BEGIN
DECLARE #TempZips TABLE (ID INT IDENTITY(1,1), Zip INT)
INSERT INTO #TempZips (Zip)
SELECT one_number + v.n
FROM (VALUES (-2), (-1), (0), (1), (2)) v(n);
END;
EDIT:
You can also use a recursive CTE:
;WITH n as (
SELECT -#posnegval as n
UNION ALL
SELECT n + 1
FROM n
WHERE n < #posnegval
)
INSERT INTO #TempZips (Zip)
SELECT #onenumber + n.n
FROM n
-- WITH OPTION (maxrecursion 0); -- only needed if you'll ever have more than 100 numbers

Just another option using an ad-hoc tally table
Declare #I int =355
Declare #R int =2
Select ID = N
,Zip= -1+#I-#R+N
From ( Select Top ((#R*2)+1) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Returns
ID Zip
1 353
2 354
3 355
4 356
5 357

Related

Divide two numbers to a range without using parts

I have to divide two numbers based on the increment provided by the users, I found an answer which is based on the parts.
I can not use parts as it effects numbers of rows in result, rather I have to pass increment.
Here is my query using parts.
declare #min numeric(18,0)
declare #max numeric(18,0)
declare #parts numeric(18,0)
select #min = 100 ,
#max = 204,
#parts = 10
declare #increment int = (#max - #min) / #parts
while #max >= #min
begin
declare #newMin numeric(18,0) = #min + #increment
print convert(varchar, #min) + ' - ' + convert(varchar, #newMin) select #min = #newMin + 1
end
Expected output, as you can see in the query my input is min and max with parts based on which the increments are calculating but I have to fix increment like 10 or 100.
From To
--------
100 110
111 121
122 132
133 143
144 154
155 165
166 176
177 187
188 198
199 204
There are 2 answers here, 1 based on the original version of the question (where To could be larger than #max) and that your goal is that #parts is the value of numbers in you want in the bucket (+1). The latter is that you want to split the range into that many buckets, and the last bucket is shrunk if the upper value is larger than the #max value. Both use a Tally function here, which I include the definition of:
CREATE FUNCTION [fn].[Tally] (#End bigint, #StartAtOne 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 #StartAtOne = 0
UNION ALL
SELECT TOP (#End)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3, N N4, N N5, N N6, N N7, N N8)
SELECT I
FROM Tally;
GO
DECLARE #min numeric(18,0) = 100,
#max numeric(18,0) = 304,
#parts numeric(18,0) = 10;
SELECT #min + (T.I * (#parts+1)),
#min + ((T.I+1) * (#parts+1))-1
FROM fn.Tally((#max - #min)/(#parts+1),0) T;
SELECT #min + (T.I * CEILING(((#max - #min)/#parts))),
CASE WHEN #min + ((T.I+1)* CEILING(((#max - #min)/#parts)))-1 > #max THEN #max ELSE #min + ((T.I+1)* CEILING(((#max - #min)/#parts)))-1 END
FROM fn.Tally(#parts-1,0) T
db<>fiddle
The question is unclear and doesn't describe the actual problem that needs solving. The code shows a way to partition a continuous range of numbers 100-204 into N partitions, in this case specified by #parts. It's not very efficient but it works.
Partitioning a range into parts is a popular SQL puzzle so there are a lot of articles over the past 30-40 years that show how to do it for different databases, using different features and trying to get the best performance. In the simple form there are two ways to partition :
By part count, what you have
By part size
If you don't want by part count, you probably want by part size. Doing this isn't complicated either and doesn't require slow loops.
Assuming we have a Tally table named Numbers, with all numbers up to eg 1M, the query to partition a range would be :
declare #start int=100
declare #end int=204
declare #size int=25
;with parts as (
select Number,(Number-#start)/#size as part_id
from numbers where number between #start and #end
)
select part_id,min(number) as [Start],max(number) as [End]
from parts
group by part_id
----------------------
part_id Start End
0 100 124
1 125 149
2 150 174
3 175 199
4 200 204
First, the Number is divided by the size we want using integer division to determine the part it belongs to. The results are then grouped by the Part ID and the range limits are the minimum and maximum Number in that group.
Creating a Numbers table is cheap. I used this script to generate a table with 1M numbers which takes about 11MB only:
DECLARE #UpperBound INT = 1000000;
;WITH cteN(Number) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
FROM sys.all_columns AS s1
CROSS JOIN sys.all_columns AS s2
)
SELECT [Number] INTO dbo.Numbers
FROM cteN WHERE [Number] <= #UpperBound;
CREATE UNIQUE CLUSTERED INDEX CIX_Number ON dbo.Numbers([Number])
WITH
(
FILLFACTOR = 100,
DATA_COMPRESSION = ROW
);
Data compression is available in all SQL Server versions and editions since SQL Server 2016 SP1.
The same technique can be used to partition a range into N parts, using the NTILE function this time :
declare #parts int=10
;with parts as (
select number, NTILE(#parts) over(order by number) as part_id
from numbers where number between #start and #end
)
select part_id,min(number) as [Start],max(number) as [End]
from parts
group by part_id
In real business cases NTILE is used to partition results into "buckets"

Difference between two columns into separate rows for each value between the two

I'm having trouble explaining my problem but I have this table
ID START END
1 10 12
2 30 31
3 11 13
and want something like this:
ID NUMBER
1 10
1 11
1 12
2 30
2 31
3 11
3 12
3 13
I need the all unique whole numbers between the two columns transform into separate rows.
Here's how I want the transformation to look like
I haven't tried anything because I don't even know how to call such procedure so any help is appreciated
If you don't have a numbers table (highly recommended), you can use an ad-hoc tally table in concert with a CROSS APPLY
Example
Select A.ID
,B.Number
From YourTable A
Cross Apply ( Select Top ([End]-[Start]+1)
Number=[START]-1+Row_Number() Over (Order By (Select NULL))
From master..spt_values n1, master..spt_values n2
) B
Returns
ID Number
1 10
1 11
1 12
2 30
2 31
3 11
3 12
3 13
In SQL Server, you can use a recursive CTE:
with cte as (
select id, [start] as number, [end] as end_number
from t
union all
select id, number + 1
from cte
where number < end_number
)
select id, number
from cte;
Note: If the span can exceed 100, you need option (maxrecursion) for the query.
Recursive CTEs are usually a bit slower than a numbers table. However, I find them much faster than I would expect.
--create table NewTable
--(
--ID int,
--NUMBER int
--)
DECLARE #ID nvarchar(50)
declare #START int
declare #END int
DECLARE Cursor_Name CURSOR
FOR
select ID, [START], [End] from tblSequences
OPEN Cursor_Name
FETCH NEXT FROM Cursor_Name INTO #ID,#START,#END
WHILE ##FETCH_STATUS = 0
begin
WHILE #START<=#END
begin
insert into NewTable (ID, NUMBER) Values (#ID, #START)
set #START = #START+1
end
FETCH NEXT FROM Cursor_Name INTO #ID,#START,#END
end
CLOSE Cursor_Name
DEALLOCATE Cursor_Name
select * from NewTable

How to insert records of the range in table when I define the range in first table in SQL Server 2012

Here I have two table, with the name Table A and Table B.
Table A:
ID From To
-------------------
1 985 992
2 1201 1207
3 1584 1589
Table B:
ID Numbers
---------------------------
1 985
2 986
3 987
4 988
5 989
6 990
7 991
8 992
9 1201
10 1202
11 1203
12 1204
13 1205
14 1206
and the number goes like this. And the table structure as well.
How can such kind of data can be insert. As I define range from 125- 135 in table A, all the number with in this range must be inserted at table B.
Thanks to all the well wisher for their valuable suggestion. Answer has been solve with using trigger.
CREATE TRIGGER trgAfterInsert on samplea
FOR INSERT
AS declare #id int, #from bigint, #to bigint, #number bigint;
select #id=i.id from inserted i;
select #from=i.fromnum from inserted i;
select #to=i.tonum from inserted i;
set #number=#from
while #number<=#to
begin
insert into sampleB (id, numbers) values (#id,#number);
set #number=#number+1
end
Finally the problem is solved. With this inserting data range in table A, data will be automatically inserted in table B with this trigger.
You can do it with a cursor and while loops,
DELCARE #Uid int, #Ustart int, #Uend int, #Ucounter;
DECLARE Ucursor CURSOR
FOR SELECT * FROM TableA ;
OPEN vend_cursor
FETCH NEXT FROM Ucursor
INTO #Uid,#Ustart,#Uend
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Ucounter = #Ustart
WHILE #Ucounter <> #Uend
BEGIN
INSERT INTO TableB
VALUES (#Ucount) -- Set the identity on for id
SET #Ucounter += 1
END
FETCH NEXT FROM Ucursor
INTO #Uid,#Ustart,#Uend
END
CLOSE Ucursor;
Not sure if this is efficient but it works.
DECLARE #range INT = (SELECT [To] - [From] FROM #tableA WHERE [Id] = 1)
DECLARE #count INT = 0
WHILE (#count <= #range)
BEGIN
INSERT INTO #tableB
SELECT [From] + #count FROM #tableA
SET #count = #count + 1
END
I would suggest a recursive CTE:
with cte as (
select from as n, from, to
from a
union all
select n + 1, from, to
from cte
where n < to
)
select n
from cte;
To create a table, you can do:
with cte as (
select from as n, from, to
from a
union all
select n + 1, from, to
from cte
where n < to
)
select identity(), n
into b
from cte;
Notes:
I left the column names as you have them without escaping them. Obviously, from and to are keywords in SQL.
If you have a gap of more than 100, you'll want to use the MAXRECURSION option.
You can insert values just as easily as creating a new table.
Try this,
declare #t table(ID int,Froms int,Tos int)
insert into #t values
(1 , 985 , 992 )
,(2 , 1201 , 1207 )
,(3 , 1584 , 1589 )
declare #table2 table(id int identity(1,1),numbers int)
insert into #table2
select number from #t t
cross apply(
select distinct number from master..spt_values
where number>t.[froms] and number<=t.tos)ca
select * from #table2

In SQL , how to build a loop that copies a row number of times

could someone please help? My starting table looks like this with 2 fields:
Name Counter
dave 2
Joe 3
I want my result to look like this:
Name Counter
dave 1
dave 2
joe 1
joe 2
joe 3
Essentially creating n number of records base on the counter and starts at 1. I tried to do a loop using counter as a variable, but the code just runs nonstop.. could someone help?
A procedural SQL Server solution:
declare #input table
(
name nvarchar(100)
,wantedrows int
,processed bit
,id uniqueidentifier
);
declare #output table
(
name nvarchar(100)
,rownum int
);
insert into #input
select 'Dave',3,0,newid()
union
select 'Joe',2,0,newid();
while exists(select * from #input where processed = 0)
begin
declare #currentid uniqueidentifier = (select top 1 id from #input where processed = 0);
declare #currentwantedrows int = (select wantedrows from #input where id = #currentid);
declare #i int = 0;
while #i < #currentwantedrows
begin
insert into #output
select name,#i+1
from #input
where id = #currentid;
set #i = #i + 1;
end;
update #input set processed = 1 where id = #currentid;
end
select name,wantedrows from #input;
select * from #output;
You can use a number-table or following trick using a system view to build a sequence:
WITH Nums AS
(
SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT Name, Counter = n
FROM Nums n CROSS JOIN Table1 t1
WHERE n BETWEEN 1 AND Counter
ORDER BY Name, Counter;
Demo
This view has only about 2000 rows, so if you need more you could use a number-table.
http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
( presuming SQL-Server )
Is a hundred copies enough?
create table #c (num)
insert into #c (num)
select 0 union
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
select T.Name, c1.num * 10 + c0.num + 1
from T, #c c1, #c c0
where c1.num * 10 + c0.num < T.Counter
drop table #c
You didn't say which version of Sybase. The old ones I've worked on didn't allow derived tables so I had to throw the values into a temp table. But you can see how to extend the idea. This may not be the best approach if this is something you need to do more than once though.

sql where statement with multiple values

In my WHERE statement I'm trying to find some entities the have BOTH conditions, I cannot use the IN statement because it works like OR and returning only one result of many, means if I am using:
WHERE R.RiskID IN(221,111)
So I will get the Documents that have or 111 OR 222
its a problem for me to use the AND statement because the proc is dynamic and generates complex code.
example:
WHERE R.RiskID IN(111,222)
Again, it works like OR statement. The defenition table have a row for each RiskID, means if I have many RiskIDs for same document it looks like:
RiskID DocumentID
111 345
222 345
333 345
999 846
111 846
my final destinations should be, if my input is 111,222 I need to show all the Documents that have 111 AND 222 RiskIDs.
if anybody have the same problem so the answer is COMBINATIONS and PERMUTATIONS...
;with list (n) as
(
select *
from
(
values
('a'),
('b'),
('c'),
('d'),
('e')
) x(y)
), cte as
(
select CAST( n AS NVARCHAR(MAX) ) n
from list c
union all
select CAST( l.n + ',' + c.n AS NVARCHAR(MAX) )
from cte c, list l
where l.n > c.n
)
select *
from cte
the generator generates all the possible combinations for the VALUES that in the FROM clouse.
Overkill for the win. Use this script for smaller query or indexed query.
I have put in the two option, one is to manually define the Riskid or uncomment the sql query to do a wildcard or enter in base on a selection.
Create table #Risk (RID Int Identity,RiskID int)
/* Manual Insert */
insert into #Risk select 111
insert into #Risk select 222
/* SQL insert */
/*
insert into #Risk select distinct RiskID from Tablename where RiskID in (111,222)
*/
Declare #Loop int
set #Loop = 1
Create table #final (RiskID int, DocumentID int)
while #Loop < (select MAX(rid)+1 from #Risk)
Begin
insert into #final
select Riskid, DocumentID
from tablename
where riskid = (select riskid from #Risk where RID = #Loop)
set #Loop = #Loop + 1
End