4

I've got two columns in the SQL database: SubnetID, SubnetName

Example:

 SubnetID  SubnetName

 1         1.2.3.0/24
 2         1.2.4.0/14
 3         1.2.5.4/30
 ...
 ...

Using SQL code, I need to add another column "IP Address" for each existing row (subnetID, SubnetName).

Output:

SubnetID  SubnetName   IP Address
 1         1.2.3.0/24   1.2.3.0
 1         1.2.3.0/24   1.2.3.1
 1         1.2.3.0/24   1.2.3.2
 ...       ...          ... and so on (till .24)
 1         1.2.3.0/24   1.2.3.24
 2         1.2.4.0/14   1.2.4.0  
 2         1.2.4.0/14   1.2.4.1 
 2         1.2.4.0/14   1.2.4.2
 ...       ...          ... and so on (till .14)
 2         1.2.4.0/14   1.2.4.14  
 3         1.2.5.4/30   1.2.5.4
 3         1.2.5.4/30   1.2.5.5
 3         1.2.5.4/30   1.2.5.6
 ...       ...          ... and so on (till .30)
 3         1.2.5.4/30   1.2.5.30
 ... etc.

So basically first two columns need to stay as is, but additional column will have individual addresses which cover the (.x/x) range.

Thank you very much for any suggestions.

Arulkumar
  • 12,966
  • 14
  • 47
  • 68
Sony107
  • 49
  • 1
  • 2
    which database system are you using? – Radim Bača Jul 25 '18 at 07:30
  • 7
    `1.2.3.0/24` does **not** mean `1.2.3.0 - 1.2.3.24` by any standard convention. If you've invented this as your own convention, please find a different way to represent it. `1.2.3.0/24` means `1.2.3.0` (or 1) through to `1.2.3.255` (or 254). Whether the first and last values in the range are included in your "IP Address" list depends on what *purpose* you'll be putting these values to. – Damien_The_Unbeliever Jul 25 '18 at 07:36
  • 2
    See [CIDR Notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation) – Damien_The_Unbeliever Jul 25 '18 at 07:38
  • 1
    With Postgres it would be quite easy to generate that ("incorrect") output. But the question is _why* do you need that table at all? At least in Postgres it would be unnecessary as you can easily test if an IP address is part of a subnet without the need to generate a list of all addresses in that subnet –  Jul 25 '18 at 08:00
  • 2
    If it turns out this is for [tag:sql-server], I'd strongly recommend you find a better way to store the subnets in the first place. I know you've said you don't want to change it but T-SQL's string manipulation functionality is weak and pulling the values into appropriate types around which we can *compute* (such as a pair of `int`s) takes a lot of string processing (I gave up on my attempt after I started writing the sixth `cross apply` that was still messing around with `SUBSTRING` calls. I could have written less but I prefer *not* to abuse PARSENAME, which many others could use) – Damien_The_Unbeliever Jul 25 '18 at 08:17
  • 1
    I am afraid Damien_The_Unbeliever is right and I will need to re-think how to convert CIDR notation to a range of IP addresses in SQL. I found a converter [link](http://magic-cookie.co.uk/iplist.html) and I see subnet (.0/24) brings back IP addresses from 0 through to 256 so it's definitely much more to it than (.from/to). I'm not good with the domain/routing/networking stuff so this is quite confusing for me. – Sony107 Jul 26 '18 at 00:39

3 Answers3

0

If I understand your problem correctly, instead of adding a new column to your table, it is better to create another table looking like this:

SubnetID  IPAddress
-------------------    
   1       1.2.3.0
   1       1.2.3.1
   1       1.2.3.2
  ...        ...
   2       1.2.4.0
  ...        ...
   3       1.2.5.4

Your original tables stays just as it is:

SubnetID  SubnetName
---------------------
   1      1.2.3.0/24
   2      1.2.4.0/14
   3      1.2.5.4/30
  ...        ...

In my opinion, this is better than what you want to do, because now we don't duplicate all the SubnetName values and we can now avoid redundancies and the so-called update anomaly.

e.g. In your proposed example, what would happen if you wanted to change the SubnetName of id 2 from 1.2.4.0/14 to 1.2.4.0/15? You would have to change every row - but using this way, you only have to make the change once.

Thomas Flinkow
  • 4,845
  • 5
  • 29
  • 65
  • Hi Thomas, thanks so much. I agree, it's better to omit the SubnetName column and only use SubnetID and the new IPAddress column. Once I get the query working, I then create a view which will pull the data from the original table and provide information about individual IPAddresses from the SubnetName. – Sony107 Jul 25 '18 at 22:31
  • Thomas, I don't need to add a column to the original table. I just need to query the original table to list individual IPAddresses for each Subnet. I will then create a view so I can easily see all IPAddresses, not just subnets. Hope it makes sense. Thanks for your time and help. – Sony107 Jul 25 '18 at 23:20
0

Try this solution,

with cte1 as (
    select *, RNUM = 
    row_number() over (partition by SubnetID order by SubnetID,SubnetName)-1 
     from demo_25_7_2018
     )
     select *,IPADDRESS=SUBSTRING(subnetname,1,charindex('/',subnetname,1)-1)+'.'+convert(varchar,rnum)
      from cte1

Sqlfiddle demo: http://sqlfiddle.com/#!18/0057d/1

let me know if this works fine.

  • Thank you very much. The output in the demo example is exactly what I need, however, the "Insert Into" part creates a table that is not in a format as mine. My table has only one record per subnet ID, SubnetName. E.g. You've got 5 rows of (1, '1.2.3.0/4') whereas I've got only one. So using recursion, I need to bring back additional rows for each SubnetID showing individual IPAddresses: ('1.2.3.0', '1.2.3.1', '1.2.3.2', '1.2.3.3', '1.2.3.4'). I don't need to create new table, just query the original table to show individual IPAddresses of each Subnet. Thanks again for your time and help. – Sony107 Jul 25 '18 at 23:16
0

In your question, I would suggest you do it on Business logic layer.

Thie is SQL server Version solution.

You can use CTE Recursion make it.

There are some main steps

  1. write the first subquery use STRING_SPLIT function to split / get the IP and Number.
  2. write the second subquery use STRING_SPLIT function to split . get the IP and MaxNumber and use CTE Recursion.
  3. use REPLACE in main query to replace the IP Number.

Query

;WITH CTE AS (
    SELECT t1.SubnetID,
           t1.SubnetName,
           MAX(CASE WHEN rn = 1 THEN v.value END) startIP, 
           MAX(CASE WHEN rn = 2 THEN v.value END) MaxLevel
    FROM T t1
    CROSS APPLY (SELECT value,row_number() over (order by (select null)) rn FROM STRING_SPLIT(t1.SubnetName,'/')) v
    GROUP BY  t1.SubnetName,
              t1.SubnetID
),CTE1 AS(
   SELECT 
          t1.SubnetID,
          t1.SubnetName,
          t1.startIP,
          CAST(t1.MaxLevel AS INT) MaxLevel,
          CAST(MAX(CASE WHEN rn = 4 THEN v.value END)  AS INT) StartLevel,
          CAST(MAX(CASE WHEN rn = 4 THEN v.value END) AS INT) IPSub
   FROM CTE t1
   CROSS APPLY (SELECT value,row_number() over (order by (select null)) rn FROM STRING_SPLIT(t1.startIP,'.')) v
   GROUP BY  
          t1.SubnetName,
          t1.startIP,
          t1.MaxLevel,
          t1.SubnetID
   UNION ALL 
   SELECT 
          SubnetID,
          SubnetName,
          startIP,
          MaxLevel,
          (StartLevel + 1) StartLevel,
          IPSub
   FROM CTE1
   WHERE StartLevel < MaxLevel
)

SELECT 
  SubnetID,
  SubnetName,
  REPLACE(startIP,CONCAT('.',IPSub),CONCAT('.',StartLevel))
FROM CTE1
order by SubnetID

SQLfiddle

EDIT

If your SQL-server version didn't support STRING_SPLIT function. you can write your own splitstring function.

Here is a custom splitstring function from T-SQL split string

CREATE FUNCTION dbo.splitstring (@inputStr VARCHAR(MAX),@spitChar varchar(1))
RETURNS
 @returnList TABLE ([Value] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(@spitChar, @inputStr) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@spitChar, @inputStr)  
  SELECT @name = SUBSTRING(@inputStr, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @inputStr = SUBSTRING(@inputStr, @pos+1, LEN(@inputStr)-@pos)
 END

 INSERT INTO @returnList
 SELECT @inputStr

 RETURN
END

Then you can use like sqlfiddle

D-Shih
  • 44,943
  • 6
  • 31
  • 51
  • Thank you very much. The problem is that my company uses SQL Server 2012 and the Split_Split isn't working. Apparently the solution is to run "ALTER DATABASE [DatabaseName] SET COMPATIBILITY_LEVEL = 130" to make this function work, but I'm not authorised to do any changes to the database settings. Thanks for your time and help. – Sony107 Jul 25 '18 at 23:36
  • @Sony107 I edit my answer you can try it. if that help you, you can mark this answer or upvote thank:) – D-Shih Jul 26 '18 at 07:31
  • Hi D-Shih, thank you so much for your time and help! The script works very well but only when there are no other zeroes '0' in the 1st, 2nd or 3rd part of the subnet. Example; 0.1.2.0/24 or 1.0.2.0/24 or 1.2.0.0/24. The script doesn't hold the zero value when creating the IP addresses but instead, increases those zeroes by 1. So example subnet 1.2.0.0/24 creates IP addresses: 1.2.0.0, 1.2.1.1, 1.2.2.2, 1.2.3.3, 1.2.4.4, 1.2.5.5, ... through to 1.2.24.24. I am not sure where the script needs correction. Any suggestion would be greatly appreciated. Thanks.Sony. – Sony107 Jul 29 '18 at 23:29
  • Ok, I fixed it. The line "REPLACE(startIP,CONCAT('.',IPSub),CONCAT('.',StartLevel))" needs to replaced by: "concat(convert(varchar(3),PARSENAME(startIP, 4)),'.', convert(varchar(3),PARSENAME(startIP, 3)),'.', convert(varchar(3),PARSENAME(startIP, 2)),'.', StartLevel) as IPAddress" – Sony107 Jul 31 '18 at 22:55