MSSQL IP addresses Update to Binary (32) - tsql

I have problem to convert my IP addresses (in table as varchar) to binary (32) format
I have table
NETWORKS
Network_IP Network_IP_in_binary subnet_of_IP subnet_of_IP_in_binary
24.55.186.203 NULL 23 NULL
24.55.185.203 NULL 45 NULL
24.55.184.203 NULL 74 NULL
24.55.181.203 NULL 45 NULL
24.55.183.203 NULL 78 NULL
... ... ... ...
select / function / update
--DECLARE #ipd as varchar(200)
--SET #ipd = (SELECT [Network_IP] FROM [Database].[dbo].[Networks] WHERE [Network_IP] = #IPd)
declare #ip varchar(200)
declare #address varchar(200)
set #address =
--#ipd
'24.55.186.203'
set #ip = parsename(#address,1)
DECLARE #Binary VARCHAR(200)
SET #Binary = ''
WHILE #IP <> 0
BEGIN
SET #Binary = SUBSTRING('0123456789', (#IP % 2) + 1, 1) + #Binary
SET #IP = #IP / 2
END
SET #IP = parsename(#address,2)
WHILE #IP <> 0
BEGIN
SET #Binary = SUBSTRING('0123456789', (#IP % 2) + 1, 1) + #Binary
SET #IP = #IP / 2
END
SET #IP = parsename(#address,3)
WHILE #IP <> 0
BEGIN
SET #Binary = SUBSTRING('0123456789', (#IP % 2) + 1, 1) + #Binary
SET #IP = #IP / 2
END
SET #IP = parsename(#address,4)
WHILE #IP <> 0
BEGIN
SET #Binary = SUBSTRING('0123456789', (#IP % 2) + 1, 1) + #Binary
SET #IP = #IP / 2
END
SELECT right('000000000000000000000000000000' + cast(#Binary as varchar),30)
I need table like this, but
I fail to setup declaration of select or update for more then one address
then I fail to figure out Update of table network
then i fail put subnet of_of_IP address into binary format
last thing I fail is update of table network and set binary format in to right column.
NETWORKS
Network_IP Network_IP_in_binary subnet_of_IP subnet_of_IP_in_binary
24.55.186.203 000110001101111011101111001011 23 000110001101111011101111001011
24.55.185.203 000110001101111011101111001011 45 000110001101111011101111001011
24.55.184.203 000110001101111011101111001011 74 000110001101111011101111001011
24.55.181.203 000110001101111011101111001011 45 000110001101111011101111001011
24.55.183.203 000110001101111011101111001011 78 000110001101111011101111001011
... ... ... ...
I read articles on sites:
SQL to find IP Address in Subnet
Datatype for storing ip address in SQL Server
But I can not figure out updates from them.

Though I agree with Panagiotis's comment, and with the accepted answer in the question you've linked to (that is, store the IP as binary(4) and not as text), You might not be in a position to decide these things (I see a lot of questions about how to work with databases that can't be changed even though an alternate design would solve all the problems) -
So, to convert an IPv4 address to a string representation of a binary value, you can use a series of bit operations to create that string, for each of the 4 parts of the IP address:
CREATE FUNCTION TinyIntToBinaryString(#value tinyint)
RETURNS CHAR(8)
AS
BEGIN
RETURN CAST(SIGN(#value & 128) as char(1)) +
CAST(SIGN(#value & 64) as char(1)) +
CAST(SIGN(#value & 32) as char(1)) +
CAST(SIGN(#value & 16) as char(1)) +
CAST(SIGN(#value & 8) as char(1)) +
CAST(SIGN(#value & 4) as char(1)) +
CAST(SIGN(#value & 2) as char(1)) +
CAST(SIGN(#value & 1) as char(1))
END
This will return a string representation of the binary value of any number between 0 to 255 - so 3 will return 00000011, and 174 will return 10101110.
Now that you have that, you can use it to convert the entire IP address to it's binary string representation:
DECLARE #IP varchar(15) = '24.55.186.203'
SELECT dbo.TinyIntToBinaryString(PARSENAME(#IP, 4)) +
dbo.TinyIntToBinaryString(PARSENAME(#IP, 3)) +
dbo.TinyIntToBinaryString(PARSENAME(#IP, 2)) +
dbo.TinyIntToBinaryString(PARSENAME(#IP, 1))
Will result with 00011000001101111011101011001011
Of course, you can create a function that will convert the entire IP string representation for you - I'll leave that up to you to write.

Related

Split the String on condition based, but not splitting the word and result should appear in column wise

I have a table which has 3 addresses (address1, address2, address3, address4) If LEN (address1) < 30 then move the rest of the string to address2 and do same check in address2 and address3 columns. For example,
IF address1='FLAT K 17TH FLOOR NO 100 NUDONG NORTH', address2='ROAD INDIA (MAANGHAI) PILOT FREE TRADE', address3='ZONE THE PRD', address4='ITALY'
from my testable I want my solution as Len(Address1,2,3,4) < 30 and words should be separated using space and not in between.
address1='FLAT K 17TH FLOOR NO 100', address2='NUDONG NORTH ROAD Italy', address3='(MAANGHAI) PILOT FREE TRADE', address4='ZONE THE PRD ITALY'
I tried SUBSTRING and CHARINDEX, but it is cutting my words in between.
After a long try, find this solution to separate the address with each column with length of 30 and not splitting the word. There may be many easy other solutions, If anyone have other solutions, please feel free to share
DECLARE #Address1 VARCHAR(MAX)
DECLARE #TempAddress2 VARCHAR(MAX)
DECLARE #TempAddress3 VARCHAR(MAX)
DECLARE #Address2 VARCHAR(MAX)
DECLARE #Address3 VARCHAR(MAX)
DECLARE #Address4 VARCHAR(MAX)
SET #Address1 ='FLAT K 17TH FLOOR NO 100 NUDONG NORTH'
SET #Address2 = 'ROAD INDIA (MAANGHAI) PILOT FREE TRADE'
SET #Address3 = 'ZONE THE PRD'
SET #Address4 = 'ITALY'
SET #TempAddress2 = SUBSTRING(#Address1,LEN(REVERSE(SUBSTRING(REVERSE(LEFT( #Address1, 30)),CHARINDEX(' ',REVERSE(LEFT( #Address1, 30))),30)))+2,LEN(#Address1)) +' '+ #Address2
SET #TempAddress3 = SUBSTRING(#TempAddress2 + ' ' + #Address3,LEN(REVERSE(SUBSTRING(REVERSE(LEFT( #TempAddress2 + ' ' + #Address3, 30)),CHARINDEX(' ',REVERSE(LEFT( #TempAddress2 + ' ' + #Address3, 30))),30)))+2,LEN(#TempAddress2 + ' ' + #Address3))
SELECT REVERSE(SUBSTRING(REVERSE(LEFT(#Address1, 30)),CHARINDEX(' ',REVERSE(LEFT(#Address1, 30))),30)) AS Address1
,SUBSTRING(#Address1,LEN(REVERSE(SUBSTRING(REVERSE(LEFT( #Address1, 30)),CHARINDEX(' ',REVERSE(LEFT( #Address1, 30))),30)))+2,LEN(#Address1)) as Left_Address1
,REVERSE(SUBSTRING(REVERSE(LEFT( #TempAddress2, 30)),CHARINDEX(' ',REVERSE(LEFT( #TempAddress2, 30))),30)) AS Address2
, SUBSTRING(#TempAddress2,LEN(REVERSE(SUBSTRING(REVERSE(LEFT( #TempAddress2, 30)),CHARINDEX(' ',REVERSE(LEFT( #TempAddress2, 30))),30)))+2,LEN(#TempAddress2)) as Address3
, SUBSTRING(#TempAddress3,LEN(REVERSE(SUBSTRING(REVERSE(LEFT( #TempAddress3, 30)),CHARINDEX(' ',REVERSE(LEFT( #TempAddress3, 30))),30)))+2,LEN(#TempAddress3)) as Address4

wrong result with floating numbers

I have this table with 1 records. Im trying to compute something call Puntaje, to get the Puntaje Result I have to follow the following formula:
Puntaje = (Infracciones * 10) / Horas
Horas = Segundos / 60 / 60
I wrote the following script, but I have some doubt and problem.
1) Is there another way to assign the values to #variables or another way to compute the sum?
2) Why the Puntaje result is 0.00, have to be: 0.854
Im using MS SQL Server 2012
Can someone help me to resolve this? Thank you in advance.
/* content of table: #Customer_Drivers
DriverId Segundos KM QtyExcesos QtyFreAce QtyDesc Puntaje IDC
6172 717243 1782 17 0 0 0 0
*/
DECLARE #Customer_Drivers TABLE (
DriverId INT,
Segundos INT,
KM INT,
QtyExcesos INT,
QtyFreAce INT,
QtyDesc INT,
Puntaje INT,
IDC INT
);
SET NOCOUNT ON;
INSERT INTO #Customer_Drivers (DriverId, Segundos, KM, QtyExcesos, QtyFreAce, QtyDesc, Puntaje, IDC)
VALUES (6172, 717243, 1782, 17, 0, 0, 0, 0);
SET NOCOUNT OFF;
DECLARE #DriverId INT = 6172;
DECLARE #Horas INT;
DECLARE #QtyExcesos INT ;
DECLARE #QtyFreAce INT ;
DECLARE #QtyDesc INT ;
DECLARE #Infracciones INT;
DECLARE #Puntaje Decimal(18,2);
SET #Horas = (SELECT Segundos FROM #Customer_Drivers WHERE DriverId = #DriverId) / 60 / 60;
SET #QtyExcesos = (SELECT QtyExcesos FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #QtyFreAce = (SELECT QtyFreAce FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #QtyDesc = (SELECT QtyDesc FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #Infracciones = (#QtyExcesos + #QtyFreAce + #QtyDesc);
SET #Puntaje = ( #Infracciones * 10) /#Horas;
PRINT #Horas
PRINT #QtyExcesos
PRINT #QtyFreAce
PRINT #QtyDesc
PRINT #Puntaje
/* OUTPUT
199 -- #Horas
17 -- #QtyExcesos
0 -- #FreAce
0 -- #QtyDesc
0.00 -- #Puntaje must be = 0.854
*/
Even though #Puntaje is declared as Decimal(18,2), that doesn't mean your calculation will be treated as a decimal. The problem is that ( #Infracciones * 10) / #Horas is using all integers so this expression will result in the integer value 0. Then this integer 0 is converted to a decimal and stored in #Puntaje.
To fix this, you need to convert part of the expression to a decimal first so that the result will be a decimal:
SET #Puntaje = ( CAST(#Infracciones AS Decimal(18,2)) * 10) / #Horas
You are using integers in your calculation, so the result will be rounded off (or truncated) to the nearest integer. Use decimal values, or use 'cast' :
#Puntaje = (cast(#Infracciones as decimal(18,2)) * 10.0) / cast(#Horas as decimal(18,2))
Check my syntax - just typed this on without trying it
1) You can use SELECT #Horas = Segundos/3600, #QtyExcesos = QtyExcesos ... FROM [RS_Reports].[dbo].[Customer_Drivers] WHERE DriverId = #DriverId. This should work providing that there is one line of results.
2) Already answered by others, you have to divide by decimal to get a decimal, i.e. you'll have to convert #Horas to Decimal
From what I've learned. #variable should be some parameters light input parameter and output parameter.... Try to execute your Stored Procedure and see what you got in SQL Server Management Studio.
There should be a return value.

Assign range to variable using between

I have the following decitiontree:
declare #placeholder varchar(20)
If #Number1 = 1
AND #Number2 = 0
BEGIN SET #placeholder = 'NULL'
END
ELSE IF #Number1 = 1
AND #Number2 > 0
BEGIN SET #placeholder = Between (#Number2*10) AND (#Number2*10+9)
END
ELSE
BEGIN
SET #placeholder = #Othervariable
END
I need the Variable for the query:
SELECT * FROM Table
WHERE #Placeholder is null or ID = #placeholder.
But the 'Between' part is not working. Can anyone help me with it?
This won't work in SQL server - there is no variable type that holds something like lambda expression, say Between (#Number2*10) AND (#Number2*10+9).
One way is to store this in string (say, nvarchar(max)) and execute using exec() or sp_executesql().
The other (usually more optimized) way is to form the main expression to include sub-expressions along with exclusion criteria.
Here is an example:
DECLARE #Number1 int = 1 -- input variable 1
DECLARE #Number2 int = 1 -- input variable 2
DECLARE #excl1 bit = 0 -- exclusion criteria 1 (ec1)
DECLARE #excl2 bit = 0 -- exclusion criteria 2 (ec2)
-- fill excl. crit.
SET #excl1 = CASE WHEN #Number1 = 1 AND #Number2 = 0 THEN 1 ELSE 0 END
SET #excl2 = CASE WHEN #Number1 = 1 AND #Number2 > 0 THEN 1 ELSE 0 END
-- just output to see what's happening
PRINT #Number1
PRINT #Number2
PRINT #excl1
PRINT #excl2
SELECT *
FROM Table
WHERE
-- if ec1 is active, we apply sub-expression 1
(#excl1 = 0 OR
(#excl1 = 1 AND ID IS NULL))
AND
-- if ec2 is active, we apply sub-expression 2
(#excl2 = 0 OR
(#excl2 = 1 AND ID BETWEEN #Number2 * 10 AND #Number2 * 10 + 9))

how does one extract octets from an inet value in postgres sql?

I would like to convert an inet formatted IPv4 address into the integer components.
For example, turn '101.255.30.40' into oct1=101, oct2=255, oct3=30, and oct4=40.
There are regex expressions that should do this if I cast the inet as a varchar, but that seems inelegant. Is there a 1-line function for returning the nth octet of an inet?
select inet_to_octet('101.255.30.40', 4) as temp; -- returns temp=40?
I finally got an excellent answer from a co-worker...
For some flavors of sql, use "split_part" along with host(inet) to get the text field.
select split_part(host('101.255.30.40'::inet), '.', 1);
select split_part(host('101.255.30.40'::inet), '.', 2);
select split_part(host('101.255.30.40'::inet), '.', 3);
select split_part(host('101.255.30.40'::inet), '.', 4);
results in
101
255
30
40
If you want to get trickier and handle IPv6, use a mask to speed up the operation along with case statements to get the IP version:
select
(case
when family('101.255.30.40'::inet) = 4 then split_part(host(broadcast(set_masklen('101.255.30.40'::inet, 32))), '.', 4)::varchar
when family('101.255.30.40'::inet) = 6 then split_part(host(broadcast(set_masklen('101.255.30.40'::inet, 64))), ':', 4)::varchar
else null end)::varchar as octet4;
select
(case
when family('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet) = 4 then split_part(host(broadcast(set_masklen('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet, 32))), '.', 4)::varchar
when family('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet) = 6 then split_part(host(broadcast(set_masklen('2604:8f00:4:80b0:3925:c69c:458:3f7b'::inet, 64))), ':', 4)::varchar
else null end)::varchar as octet4;
results in
40
80b0
you can then add in a hex-to-int conversion into the case statement if you want to cast the IPv6 as a number instead.
Unless you want to try to contribute the function to the inet datatype, you'll need to rely on a string based version. Maybe put something like this (but with some error checking) into an SQL function for easy access?:
CREATE OR REPLACE FUNCTION extract_octet(inet, integer) RETURNS integer AS $$
SELECT ((regexp_split_to_array(host($1), E'\\.'))[$2])::int;
$$ LANGUAGE SQL;
select extract_octet(inet '192.26.22.2', 2)
Output: 26
Here are a couple of one-liners for the separate octets of an IPv4 address:
SELECT substring(host('1.2.3.4'::inet) FROM '^([0-9]+)\.[0-9]+\.[0-9]+\.[0-9]+$');
will return 1
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.([0-9]+)\.[0-9]+\.[0-9]+$');
will return 2
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.[0-9]+\.([0-9]+)\.[0-9]+$');
will return 3
SELECT substring(host('1.2.3.4'::inet) FROM '^[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)$');
will return 4

tsql how to validate a number's scale

I need to validate the number of digits to the right of the decimal (the scale)
0, is a valid number in any of the places (tenths, hundredths, thousandths, etc.).
Any tips or tricks?... w/o an extensive regex library, and no built in function, I would prefer a function that accepts the number, the number of places the scale should equal, and then return a bit.
Following up with Maess's suggestion I came up with this:
CREATE FUNCTION [dbo].[GetScale]
(
#tsValue varchar(250)
, #tiScale int
)
RETURNS int
AS
BEGIN
DECLARE
#tiResult int
, #tiValueScale int
SET #tiResult = 0
SELECT #tiValueScale = LEN( SUBSTRING ( #tsValue, PATINDEX('%.%', #tsValue) + 1, LEN(#tsValue) ) )
IF (#tiValueScale = #tiScale)
SET #tiResult = 1
RETURN #tiResult
END
GO
Seems to work as desired. Thanks for the help.
Just as a followup... i ran into an issue where a number didnt have a decimal (which returns the patindex to 0) and the number was the same size as the scale, it would return a false positive... so i add an additional select from the patindex to determine if it does exist or not... it now looks like this:
- =============================================
ALTER FUNCTION [dbo].[GetScale]
(
#tsValue varchar(250)
, #tiScale int
)
RETURNS int
AS
BEGIN
DECLARE
#tiResult int
, #tiValueScale int
, #tiDecimalExists int
SET #tiResult = 0
SET #tiDecimalExists = 0
SELECT #tiDecimalExists = PATINDEX('%.%', #tsValue)
IF (#tiDecimalExists != 0)
BEGIN
SELECT #tiValueScale = LEN( SUBSTRING ( #tsValue, #tiDecimalExists + 1, LEN(#tsValue) ) )
IF (#tiValueScale = #tiScale)
SET #tiResult = 1
END
RETURN #tiResult
END
I tried Anthony's solution with some success, but there is some undesirable side effects when the first whole number is 9.
For example...
select 0.11 as fraction, Math.NumberOfDecimalPlaces(0.11) dp union
select 9.1, Math.NumberOfDecimalPlaces(9.1) union
select 9.01, Math.NumberOfDecimalPlaces(9.01) union
select 9.0, Math.NumberOfDecimalPlaces(9.0) union
select 99.0, Math.NumberOfDecimalPlaces(99.0) union
select 10999.0, Math.NumberOfDecimalPlaces(10999.0) union
select 8.0, Math.NumberOfDecimalPlaces(8.0) union
select 0, Math.NumberOfDecimalPlaces(0)
Produces...
0.00 0
0.11 2
8.00 0
9.00 -1
9.01 2
9.10 1
99.00 -2
10999.00 -3
Which shows some incorrect calculations when 9 is the first whole number.
I've made a small improvement to Anthony's original function.
CREATE FUNCTION [Math].[NumberOfDecimalPlaces]
(
#fraction decimal(38,19)
)
RETURNS INT
AS
BEGIN
RETURN FLOOR(LOG10(REVERSE(ABS(#fraction % 1) +1))) +1
END
This simply strips of the whole number part of the fraction. Which when implemented produces...
0.00 0
0.11 2
8.00 0
9.00 0
9.01 2
9.10 1
99.00 0
10999.00 0
The correct result
CREATE FUNCTION dbo.DecimalPlaces(#n decimal(38,19))
RETURNS int
AS
BEGIN
RETURN FLOOR(LOG10(REVERSE(ABS(#n % 1) + 1))) + 1
END
Edit (Feb 5 '15):
Thanks sqlconsumer. I have included your fix for nines before the decimal point.