Looping within SQL Server - Iteration of IPs - sql

Sample Input
Sample Output
I believe this could be solved using WHILE Loop but not able to figure out how to unpivot it into a single column and at the same time increment only the last number.
EDIT: IP Ranges are made by me, I picked them randomly.

I believe I have solved your issue. With this technique we also solve the issue mentioned by Larnu in the comment.
DECLARE #tbl TABLE (IP_start varchar(15), IP_end varchar(15))
DECLARE #i int=1
INSERT INTO #tbl (IP_start, IP_end)
VALUES ('121.11.32.0','121.11.32.5')
,('121.11.33.11','121.11.33.11')
,('121.11.34.0','121.11.34.4')
/*,('121.11.35.0','121.11.36.15')*/
;WITH cte
AS
(SELECT
t.IP_start, t.IP_end
,CAST(CAST(PARSENAME(t.IP_start,4) AS int) AS binary(1))+CAST(CAST(PARSENAME(t.IP_start,3) AS int) AS binary(1))+CAST(CAST(PARSENAME(t.IP_start,2) AS int) AS binary(1))+CAST(CAST(PARSENAME(t.IP_start,1) AS int) AS binary(1)) AS ip_Bin
FROM #tbl t
UNION ALL
SELECT
tt.IP_start, tt.IP_end
,cast((cast(cte.ip_Bin AS int)+#i) AS binary(4))
FROM cte INNER JOIN
#tbl tt ON tt.IP_start=cte.IP_start AND cte.IP_end=tt.IP_end
WHERE cte.ip_Bin<CAST(CAST(PARSENAME(tt.IP_end,4) AS int) AS binary(1))+CAST(CAST(PARSENAME(tt.IP_end,3) AS int) AS binary(1))+CAST(CAST(PARSENAME(tt.IP_end,2) AS int) AS binary(1))+CAST(CAST(PARSENAME(tt.IP_end,1) AS int) AS binary(1))
)
SELECT CAST(CAST(SUBSTRING(cte.ip_Bin,1,1) AS int) AS varchar(3))+'.'+ CAST(CAST(SUBSTRING(cte.ip_Bin,2,1) AS int) AS varchar(3))+'.'+ CAST(CAST(SUBSTRING(cte.ip_Bin,3,1) AS int) AS varchar(3))+'.'+ CAST(CAST(SUBSTRING(cte.ip_Bin,4,1) AS int) AS varchar(3))
FROM cte
ORDER BY cte.IP_start
OPTION(MAXRECURSION 0)

I'll leave this answer in case it helps anyone. This is for Postgres however.
It is not clear what the base types are, but you can use inet functions for this -- by converting to an int and back again:
select ip + '0.0.0.0'::inet, (ip + '0.0.0.0'::inet)::text, host(ip + '0.0.0.0'::inet)
from t cross join lateral
generate_series(ips::inet - '0.0.0.0'::inet, ipe::inet - '0.0.0.0'::inet, 1) ip;
Here is a db<>fiddle.
The first column is an inet representation of the value. The second is a string representation, but using standard IPV4 notation. The final looks like the input data.

Related

Create a function with two parameters and list numbers between them

I need to list all numbers between user input's. Actually I need to list only the odd numbers but I can do that if I can list all numbers.
Here's what I have done so far
ALTER Function [dbo].[ofdnumbers]
(
#MaxValue as int,
#MinValue as int
)
Returns #table table( Sonuclar int not null)
as
Begin
Declare #Num as int
set #Num=#MinValue
set #Num=#Num+1
insert into #table
select sonuclar from #table
return #table
end
One possible function you could implement would be something along the lines of the following.
As illustrated by the various linked articles there are numerous ways to create a number series, the best approach is probably to have a permanent numbers/tally table with enough rows to cater for all requirements that you can just directly select from.
The following (or something similar) will provide the best relative performance:
create or alter function dbo.OddNumbers(#MaxValue int, #MinValue int)
returns table
as
return
select Sonuclar from (
select top (#MaxValue - #MinValue + 1)
Row_Number() over(order by (select null)) + #MinValue - 1 as Sonuclar
from master.dbo.spt_values
)n
where Sonuclar % 2 = 1;

How to concatenate string of consecutive numbers (in a range) in SQL with a loop?

This question may have been answered, but, I might have a different problem to solve.
I have the following string: '123456' and the following range: 05 - 10
How can I concatenate the string with each number in the range, so I have:
12345605
12345606
12345607
12345608
12345609
12345610
I will have a stored procedure that accepts 6 digits code, min and max numbers in the range. Then I will have to do some kind of processing of the data. It can be inserted later on or deleted or any sort of things.
This is what I will have in my stored procedure:
declare
#firstSix varchar(6),
#min varchar(2),
#max varchar(2)
To do that I have started to write a loop:
WHILE(CAST(#min AS int) <= CAST(#max AS int)
BEGIN
--here I will need to write a logic that concatenates the string.
SET #min = #min + 1
SELECT #firstSix
END
So, using the following input
exec myProcedure '123456', '05', '10'
I will have, the following output:
12345605
12345606
12345607
12345608
12345609
12345610
or using the following input
exec myProcedure '123456', '15', '20'
I will have the following output:
12345615
12345616
12345617
12345618
12345619
12345620
I will, probably need to have a logic to determine if a value in the range has a leading zero or something
What is the best approach here?
You want recursive CTE :
with cte as (
select #firstSix as firstSix , cast(#min as int) as mn, cast(#max as int) as mx
union all
select firstSix, mn + 1 , mx
from cte
where mn < mx
)
select concat(firstSix, right(concat('0', mn), 2))
from cte c;
By default is has 100 recursion level if you have a large suffix then query hint option (maxrecursion 0).
EDIT : Using LOOP
DECLARE #table table (firstSix varchar(255))
WHILE(CAST(#min AS int) <= CAST(#max AS int))
BEGIN
--here I will need to write a logic that concatenates the string.
INSERT INTO #table (firstSix)
SELECT concat(#firstSix, right(concat('0', #min), 2))
SET #min = #min + 1
END
SELECT firstSix
FROM #table;

SQL search in two columns with one combined value

I will try to demonstrate what I am trying to achieve. This is an oversimplified example for my case.
Suppose I have a table contains two columns
ID YEAR
--- ----
1 2017
2 2018
and I have a search term 2017 / 1
What I want to do is something like this
select * from table where 'YEAR / ID' LIKE '%searchterm%'
Is this possible ?
Thanks in advance.
In my opinion the most effective way is:
Firstly divide String x = "2017 / 1" to two int values int year = 2017, int id = 1. I don't know what kind of programing language you are using but all of programing languages have special functions to make it easily (between all values you have '/').
Then use this query:
Select *
from table
where year = 2017
and id = 1
Use Below query, I have considered your search text format as 2017 / 1.
DECLARE #tblTest AS Table
(
Id INT,
YearNo INT
)
INSERT INTO #tblTest values (1,2017)
INSERT INTO #tblTest values (2,2018)
INSERT INTO #tblTest values (3,2017)
INSERT INTO #tblTest values (4,2018)
DECLARE #searchterm VARCHAR(50)='2017 / 1'
LEFT will give you string starting from left position to applied length.
RIGHT will give you string starting from right position to applied length
SELECT
*
FROM #tblTest
WHERE YearNo=LEFT(#searchterm,4)
AND Id = REPLACE(RIGHT(#searchterm,LEN(#searchterm)-(CHARINDEX('/',(REPLACE(#searchterm, ' ', ''))))),'/','')
If your database compatibility could be 130 then You can Try String_Split ref https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
Sql most long awaited function (as msdn says)
Declare #tbl table (id int Identity(1,1), value nvarchar(5))
Insert into #tbl ([value]) SELECT value from STRING_SPLIT(#searchstring,'/')
Declare #id int
Select #id = cast(value as int) from #tbl where id=2 --will give 1
Declare #value int
Select #id = cast(value as int) from #tbl where id=1 --ill give 2017
-- —now use them in sql
select * from table where YEAR=#value and ID = #id
You are going to screw up the performance if you do anything like below
select * from table where 'YEAR / ID' LIKE '%searchterm%'
Best way is you can split your search and supply to respective col
Declare #Search varchar(15)='2017/1'
Declare #Year int = (select LEFT(#Search,CHARINDEX('/',#search)-1))
Declare #month int = (select Right(#Search,(len(#search) -CHARINDEX('/',#search))))
select * from #temp where id=#month and year=#Year
Try this code :
select * from table where YEAR + ' / ' + ID LIKE '%searchterm%'
this query will run, but it will perform very poor.

Generate a list with string prefix in SQL with fixed length

I just want to generate a list like this
XY0001
XY0002
XY0003
The prefix is same for all rows. Need fixed length (6 in this example)
Looking for an easy way to produce such list to put it into temp table.
MS SQL
for a very small number this would do:
DECLARE #TempList TABLE (Name VARCHAR(100));
insert into #TempList Values ('XY00001')
insert into #TempList Values ('XY00002')
insert into #TempList Values ('XY00003')
insert into #TempList Values ('XY00004')
select * from #TempList
You can use an ad-hoc tally table
If 2012+
DECLARE #TempList TABLE (Name VARCHAR(100));
Select Name = 'XY'+format(N,'0000')
From (Select Top 9999 N=Row_Number() Over (Order By (Select NULL)) From master..spt_values N1,master..spt_values N2) A
Order by N
Returns
Name
XY0001
XY0002
XY0003
XY0004
...
XY9997
XY9998
XY9999
If not
DECLARE #TempList TABLE (Name VARCHAR(100));
Select Name = 'XY'+right('00000'+cast(N as varchar(25)),4)
From (Select Top 9999 N=Row_Number() Over (Order By (Select NULL)) From master..spt_values N1,master..spt_values N2) A
Order by N
I like to use recursive CTE's for this.
declare #max_number int = 1000;
with num as (
select 1 as n
union
select n + 1
from num
where n < #max_number
)
select 'XY' + (cast n as char(4))
from num;
The recursive CTE gives you the numbers and the cast does the left-padding with 0's to ensure you get 0001 instead of 1.
This approach will support a variable number of outputs. Though as you alluded to in your question, this is overkill if you only want a few.
(You'll need to test this out for boundary cases. I haven't tested this exact code sample.)
There is likely a limit to how far this scales because it uses recursion.

Define variable to use with IN operator (T-SQL)

I have a Transact-SQL query that uses the IN operator. Something like this:
select * from myTable where myColumn in (1,2,3,4)
Is there a way to define a variable to hold the entire list "(1,2,3,4)"? How should I define it?
declare #myList {data type}
set #myList = (1,2,3,4)
select * from myTable where myColumn in #myList
DECLARE #MyList TABLE (Value INT)
INSERT INTO #MyList VALUES (1)
INSERT INTO #MyList VALUES (2)
INSERT INTO #MyList VALUES (3)
INSERT INTO #MyList VALUES (4)
SELECT *
FROM MyTable
WHERE MyColumn IN (SELECT Value FROM #MyList)
DECLARE #mylist TABLE (Id int)
INSERT INTO #mylist
SELECT id FROM (VALUES (1),(2),(3),(4),(5)) AS tbl(id)
SELECT * FROM Mytable WHERE theColumn IN (select id from #mylist)
There are two ways to tackle dynamic csv lists for TSQL queries:
1) Using an inner select
SELECT * FROM myTable WHERE myColumn in (SELECT id FROM myIdTable WHERE id > 10)
2) Using dynamically concatenated TSQL
DECLARE #sql varchar(max)
declare #list varchar(256)
select #list = '1,2,3'
SELECT #sql = 'SELECT * FROM myTable WHERE myColumn in (' + #list + ')'
exec sp_executeSQL #sql
3) A possible third option is table variables. If you have SQl Server 2005 you can use a table variable. If your on Sql Server 2008 you can even pass whole table variables in as a parameter to stored procedures and use it in a join or as a subselect in the IN clause.
DECLARE #list TABLE (Id INT)
INSERT INTO #list(Id)
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
SELECT
*
FROM
myTable
JOIN #list l ON myTable.myColumn = l.Id
SELECT
*
FROM
myTable
WHERE
myColumn IN (SELECT Id FROM #list)
Use a function like this:
CREATE function [dbo].[list_to_table] (#list varchar(4000))
returns #tab table (item varchar(100))
begin
if CHARINDEX(',',#list) = 0 or CHARINDEX(',',#list) is null
begin
insert into #tab (item) values (#list);
return;
end
declare #c_pos int;
declare #n_pos int;
declare #l_pos int;
set #c_pos = 0;
set #n_pos = CHARINDEX(',',#list,#c_pos);
while #n_pos > 0
begin
insert into #tab (item) values (SUBSTRING(#list,#c_pos+1,#n_pos - #c_pos-1));
set #c_pos = #n_pos;
set #l_pos = #n_pos;
set #n_pos = CHARINDEX(',',#list,#c_pos+1);
end;
insert into #tab (item) values (SUBSTRING(#list,#l_pos+1,4000));
return;
end;
Instead of using like, you make an inner join with the table returned by the function:
select * from table_1 where id in ('a','b','c')
becomes
select * from table_1 a inner join [dbo].[list_to_table] ('a,b,c') b on (a.id = b.item)
In an unindexed 1M record table the second version took about half the time...
I know this is old now but TSQL => 2016, you can use STRING_SPLIT:
DECLARE #InList varchar(255) = 'This;Is;My;List';
WITH InList (Item) AS (
SELECT value FROM STRING_SPLIT(#InList, ';')
)
SELECT *
FROM [Table]
WHERE [Item] IN (SELECT Tag FROM InList)
Starting with SQL2017 you can use STRING_SPLIT and do this:
declare #myList nvarchar(MAX)
set #myList = '1,2,3,4'
select * from myTable where myColumn in (select value from STRING_SPLIT(#myList,','))
DECLARE #myList TABLE (Id BIGINT) INSERT INTO #myList(Id) VALUES (1),(2),(3),(4);
select * from myTable where myColumn in(select Id from #myList)
Please note that for long list or production systems it's not recommended to use this way as it may be much more slower than simple INoperator like someColumnName in (1,2,3,4) (tested using 8000+ items list)
slight improvement on #LukeH, there is no need to repeat the "INSERT INTO":
and #realPT's answer - no need to have the SELECT:
DECLARE #MyList TABLE (Value INT)
INSERT INTO #MyList VALUES (1),(2),(3),(4)
SELECT * FROM MyTable
WHERE MyColumn IN (SELECT Value FROM #MyList)
No, there is no such type. But there are some choices:
Dynamically generated queries (sp_executesql)
Temporary tables
Table-type variables (closest thing that there is to a list)
Create an XML string and then convert it to a table with the XML functions (really awkward and roundabout, unless you have an XML to start with)
None of these are really elegant, but that's the best there is.
If you want to do this without using a second table, you can do a LIKE comparison with a CAST:
DECLARE #myList varchar(15)
SET #myList = ',1,2,3,4,'
SELECT *
FROM myTable
WHERE #myList LIKE '%,' + CAST(myColumn AS varchar(15)) + ',%'
If the field you're comparing is already a string then you won't need to CAST.
Surrounding both the column match and each unique value in commas will ensure an exact match. Otherwise, a value of 1 would be found in a list containing ',4,2,15,'
As no one mentioned it before, starting from Sql Server 2016 you can also use json arrays and OPENJSON (Transact-SQL):
declare #filter nvarchar(max) = '[1,2]'
select *
from dbo.Test as t
where
exists (select * from openjson(#filter) as tt where tt.[value] = t.id)
You can test it in
sql fiddle demo
You can also cover more complicated cases with json easier - see Search list of values and range in SQL using WHERE IN clause with SQL variable?
This one uses PATINDEX to match ids from a table to a non-digit delimited integer list.
-- Given a string #myList containing character delimited integers
-- (supports any non digit delimiter)
DECLARE #myList VARCHAR(MAX) = '1,2,3,4,42'
SELECT * FROM [MyTable]
WHERE
-- When the Id is at the leftmost position
-- (nothing to its left and anything to its right after a non digit char)
PATINDEX(CAST([Id] AS VARCHAR)+'[^0-9]%', #myList)>0
OR
-- When the Id is at the rightmost position
-- (anything to its left before a non digit char and nothing to its right)
PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR), #myList)>0
OR
-- When the Id is between two delimiters
-- (anything to its left and right after two non digit chars)
PATINDEX('%[^0-9]'+CAST([Id] AS VARCHAR)+'[^0-9]%', #myList)>0
OR
-- When the Id is equal to the list
-- (if there is only one Id in the list)
CAST([Id] AS VARCHAR)=#myList
Notes:
when casting as varchar and not specifying byte size in parentheses the default length is 30
% (wildcard) will match any string of zero or more characters
^ (wildcard) not to match
[^0-9] will match any non digit character
PATINDEX is an SQL standard function that returns the position of a pattern in a string
DECLARE #StatusList varchar(MAX);
SET #StatusList='1,2,3,4';
DECLARE #Status SYS_INTEGERS;
INSERT INTO #Status
SELECT Value
FROM dbo.SYS_SPLITTOINTEGERS_FN(#StatusList, ',');
SELECT Value From #Status;
Most of these seem to focus on separating-out each INT into its own parenthetical, for example:
(1),(2),(3), and so on...
That isn't always convenient. Especially since, many times, you already start with a comma-separated list, for example:
(1,2,3,...) and so on...
In these situations, you may care to do something more like this:
DECLARE #ListOfIds TABLE (DocumentId INT);
INSERT INTO #ListOfIds
SELECT Id FROM [dbo].[Document] WHERE Id IN (206,235,255,257,267,365)
SELECT * FROM #ListOfIds
I like this method because, more often than not, I am trying to work with IDs that should already exist in a table.
My experience with a commonly proposed technique offered here,
SELECT * FROM Mytable WHERE myColumn IN (select id from #mylist)
is that it induces a major performance degradation if the primary data table (Mytable) includes a very large number of records. Presumably, that is because the IN operator’s list-subquery is re-executed for every record in the data table.
I’m not seeing any offered solution here that provides the same functional result by avoiding the IN operator entirely. The general problem isn’t a need for a parameterized IN operation, it’s a need for a parameterized inclusion constraint. My favored technique for that is to implement it using an (inner) join:
DECLARE #myList varchar(50) /* BEWARE: if too small, no error, just missing data! */
SET #myList = '1,2,3,4'
SELECT *
FROM myTable
JOIN STRING_SPLIT(#myList,',') MyList_Tbl
ON myColumn = MyList_Tbl.Value
It is so much faster because the generation of the constraint-list table (MyList_Tbl) is executed only once for the entire query execution. Typically, for large data sets, this technique executes at least five times faster than the functionally equivalent parameterized IN operator solutions, like those offered here.
I think you'll have to declare a string and then execute that SQL string.
Have a look at sp_executeSQL