I am using a try catch block to catch the data with constrain errors.
eg. if null inserted in not null column or if duplicated record inserted or the type mismatch occurs, all the source records with error should go to error log table and rest of the records should go to destination table.
for this i am using try catch so i can't use bulk insert, hence using row by row insert using While Loop, which is takes forever to run as i have to insert 3000000 records.
is there any way where i can improve performance while loop? so it can insert 3000000 records in minimum time? currently it is taking 2 hours or more :(
Try conducting your insert in batches. For instance do a loop attempting to insert 10,000/1,000/100 records at a time as a bulk insert. If there is an error in the batch, catch it and re-execute that batch as a row by row operation. You will have to play with the batch size and make it small enough so that the majority of batches are processed as bulk inserts and only have to row by row a batch occasionally.
The following demonstrates handling a pile of sample data in batches with "binary search" on the batch size in the event of an error.
set nocount on;
-- Set the processing parameters.
declare #InitialBatchSize as Int = 1024;
declare #BatchSize as Int = #InitialBatchSize;
-- Create some sample data with somewhat random Divisor values.
declare #RowsToProcess as Int = 10000;
declare #SampleData as Table ( Number Int, Divisor Int );
with Digits as ( select Digit from ( values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9) ) as Digits( Digit ) ),
Numbers as (
select ( ( ( Ten_4.Digit * 10 + Ten_3.Digit ) * 10 + Ten_2.Digit ) * 10 + Ten_1.Digit ) * 10 + Ten_0.Digit + 1 as Number
from Digits as Ten_0 cross join Digits as Ten_1 cross join Digits as Ten_2 cross join
Digits as Ten_3 cross join Digits as Ten_4 )
insert into #SampleData
select Number, Abs( Checksum( NewId() ) ) % 1000 as Divisor -- Adjust "1000" to vary the chances of a zero divisor.
from Numbers
where Number < #RowsToProcess;
-- Process the data.
declare #FailedRows as Table ( Number Int, Divisor Int, ErrorMessage NVarChar(2048) );
declare #BitBucket as Table ( Number Int, Divisor Int, Quotient Int );
declare #RowCount as Int = 1; -- Force at least one loop execution.
declare #LastProcessedNumber as Int = 0;
while #RowCount > 0
begin
begin try
-- Subject-to-failure INSERT .
insert into #BitBucket
select top ( #BatchSize ) Number, Divisor, 1 / Divisor as Quotient
from #SampleData
where Number > #LastProcessedNumber
order by Number;
set #RowCount = ##RowCount;
select #LastProcessedNumber = Max( Number ) from #BitBucket;
print 'Processed ' + Cast( #RowCount as VarChar(10) ) + ' rows.';
end try
begin catch
if #BatchSize > 1
begin
-- Try a smaller batch.
set #BatchSize /= 2;
end
else
begin
-- This is a failing row. Log it with the error and reset the batch size.
set #LastProcessedNumber += 1;
print 'Row failed. Row number ' + Cast( #LastProcessedNumber as VarChar(10) ) + ', error: ' + Error_Message() + '.';
insert into #FailedRows
select Number, Divisor, Error_Message()
from #SampleData
where Number = #LastProcessedNumber;
set #BatchSize = #InitialBatchSize;
end
end catch
end;
-- Dump the results.
select * from #FailedRows order by Number;
select * from #SampleData order by Number;
select * from #BitBucket order by Number;
ex.IPv6 address with CIDR:
2620:0:2d0:200::7/32
out put
Start Range: 2620:0:0:0:0:0:0:0
End Range: 2620:0:ffff:ffff:ffff:ffff:ffff:ffff
how to calculate with PL/SQL ?
Once I wrote a general PL/SQL Package where you can do such conversions. It works for both, IPv4 and IPv6.
CREATE OR REPLACE PACKAGE IP_Util AS
/**
* Convert an IP-Address into decimal value.
* #param IP The IP-Address, e.g. '10.151.20.224' or '1080::8:800:200C:417A'.
* Supports also mixed notation like '0:0:0:0:0:FFFF:129.144.52.38'. CIDR value (e.g. '1080::8:800:200C:417A/80') is ignored.
* #return The decimal equivalent
*/
FUNCTION IP2Decimal(IP IN VARCHAR2) RETURN NUMBER DETERMINISTIC;
/**
* Convert an IP-Address into RWA value.
* #param IP The IP-Address, e.g. '10.151.20.224' or '1080::8:800:200C:417A'.
* Supports also mixed notation like '0:0:0:0:0:FFFF:129.144.52.38'. CIDR value (e.g. '1080::8:800:200C:417A/80') is ignored.
* #param ver IP version, either 4 or 6. If NULL then function determines the IP version.
* #return The RAW equivalent
*/
FUNCTION IP2RAW(IP IN VARCHAR2, ver IN INTEGER DEFAULT NULL) RETURN RAW DETERMINISTIC;
/**
* Convert an IP-Address from decimal value into IPv4 or IPv6 format.
* #param ip Decimal IP-Address, 0..(2**32)-1 or 0..(2**128)-1
* #param ver IP version, either 4 or 6
* #return The IP in IPv4 or IPv6 format
*/
FUNCTION Decimal2IP(ip IN NUMBER, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC;
/**
* Convert an IP-Address from RAW value into IPv4 or IPv6 format.
* #param ip RAW value of IP-Address, 0..FFFFFFFF or 0..FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
* #param ver IP version, either 4 or 6
* #return The IP in IPv4 or IPv6 format
*/
FUNCTION RAW2IP(ip IN RAW, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns SubnetMask of given IP-Subnet in CIDR notation.
* #param Ip Subnet IP-Address with CIDR notation, e.g. '10.152.10.17/24' or '1080::8:800:200C:417A/60'
* #return SubnetMask Subnet mask of IP-Subnet, e.g. '255.255.255.0' or 'ffff:ffff:ffff::'
*/
FUNCTION SubnetMask(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Network prefix of given IP-Subnet. In IPv4 this address was called subnet address.
* #param Ip IP-Address of subnet, e.g. '10.152.10.17' or '1080:0:100:8:800:200C:FFFF:417A'
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return Network prefix, i.e. the first address from subnet, for example '10.152.0.0' or '1080:0:100:8:800:200C:FFFF::'
*/
FUNCTION NetworkPrefix(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Network prefix of given IP-Subnet. In IPv4 this address was called subnet address.
* #param Ip IP-Subnet with CIDR notation, e.g. '10.152.10.17/24' or '1080:0:100:8:800:200C:FFFF:417A/60'
* #return Network prefix, i.e. the first address from subnet, for example '10.152.0.0' or '1080:0:100:8:800:200C:FFFF::'
*/
FUNCTION NetworkPrefix(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Broadcast address of given IP-Subnet.
* IPv6 does not provide Broadcast anymore. However, function supports IPv6 for internal purpose.
* #param Ip IP-Address of subnet, e.g. '10.152.10.17' or '1080:0:100:8:800:200C:FFFF:417A'
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return Broadcast address, i.e. the last address from subnet, for example '10.152.10.255' or '1080:0:100:8:800:ffff:ffff:ffff'
*/
FUNCTION BroadcastIp(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Returns Broadcast address of given IP-Subnet.
* IPv6 does not provide Broadcast anymore. However, function supports IPv6 for internal purpose.
* #param Ip IP-Subnet with CIDR notation, e.g. '10.152.10.17/24' or '1080:0:100:8:800:200C:FFFF:417A/60'
* #return Broadcast address, i.e. the last address from subnet, for example '10.152.10.255' or '1080:0:100:8:800:ffff:ffff:ffff'
*/
FUNCTION BroadcastIp(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
/**
* Translate Subnet mask to CIDR.
* #param SubnetMask Subnet mask of subnet, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
* #return CIDR value, e.g. 26
*/
FUNCTION SubnetMask2CIDR(SubnetMask VARCHAR2) RETURN INTEGER RESULT_CACHE DETERMINISTIC;
/**
* Translate CIDR to Subnet mask in IPv4 or IPv6 format.
* #param CIDR Length of network prefix
* #param ver IP version, either 4 or 6
* #return Subnet mask, e.g. '255.255.0.0' or 'FFFF:FFFF:FFFF::'
*/
FUNCTION CIDR2SubnetMask(CIDR IN INTEGER, ver IN INTEGER) RETURN VARCHAR2 RESULT_CACHE DETERMINISTIC;
/**
* Returns full uncompressed IPv6 Address. Mainly used for internal purpose like conversion, storage, comparison, etc.
* '::' is replaced by zero pads, leading '0' are inserted (if leadingZero = TRUE), converted to lower cases.
* #param Ip Compact IPv6-Address (with CIDR or without CIDR, e.g. 2620:0:2D0:A2A2::7)
* #param leadingZero If TRUE then bit fields are padded with '0' in order to have always 4 characters
* #return The full IPv6 Address with 8 x 16 bits, e.g. '2620:0000:02d0:a2a2:0000:0000:0000:0007'
*/
FUNCTION UncompressIpV6(Ip IN VARCHAR2, leadingZero IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 DETERMINISTIC;
/**
* Makes an canonical IPv6 address according to RFC 5952, i.e. human readable.
* #param IPv6 IPv6-Address (with or without '::', with or without leading '0')
* #return Canonical IPv6 Address, e.g. 2620:0:2d0:200::7
*/
FUNCTION Canonical_IPv6(IPv6 IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
END IP_Util;
/
CREATE OR REPLACE PACKAGE BODY IP_Util AS
NUMERIC_OVERFLOW EXCEPTION;
PRAGMA EXCEPTION_INIT(NUMERIC_OVERFLOW, -1426);
FUNCTION IP2Decimal(IP IN VARCHAR2) RETURN NUMBER DETERMINISTIC IS
DecimalIp NUMBER; -- INTEGER does not cover (2**128)-1
BEGIN
IF REGEXP_LIKE(IP, ':') THEN
-- IPv6 Address
IF REGEXP_LIKE(IP, '\d+\.\d+\.\d+\.\d+') THEN
-- Mixed notation, e.g.: 0:0:0:0:0:FFFF:129.144.52.38
SELECT SUM(TO_NUMBER(REGEXP_SUBSTR(UncompressIpV6(IP), '[[:xdigit:]]+', 1, LEVEL), 'XXXX') * POWER(65536, 8-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 6;
SELECT DecimalIp + SUM(REGEXP_SUBSTR(REGEXP_SUBSTR(UncompressIpV6(IP), '\d+\.\d+\.\d+\.\d+'), '\d+', 1, LEVEL) * POWER(256, 4-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 4;
RETURN DecimalIp;
ELSE
SELECT SUM(TO_NUMBER(REGEXP_SUBSTR(UncompressIpV6(IP), '[[:xdigit:]]+', 1, LEVEL), 'XXXX') * POWER(65536, 8-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 8;
RETURN DecimalIp;
END IF;
ELSE
-- IPv4 Address
SELECT SUM(REGEXP_SUBSTR(IP, '\d+', 1, LEVEL) * POWER(256, 4-LEVEL))
INTO DecimalIp
FROM dual
CONNECT BY LEVEL <= 4;
RETURN DecimalIp;
END IF;
END IP2Decimal;
FUNCTION IP2RAW(IP IN VARCHAR2, ver IN INTEGER DEFAULT NULL) RETURN RAW DETERMINISTIC IS
BEGIN
IF ver IS NULL THEN
IF REGEXP_LIKE(IP, ':') THEN
RETURN IP2RAW(IP, 6);
ELSE
RETURN IP2RAW(IP, 4);
END IF;
ELSE
IF ver = 6 THEN
RETURN HEXTORAW(LPAD(TO_CHAR(IP2Decimal(ip), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'), 32, '0'));
ELSIF ver = 4 THEN
RETURN HEXTORAW(LPAD(TO_CHAR(IP2Decimal(ip), 'fmXXXXXXXX'), 8, '0'));
ELSE
RAISE VALUE_ERROR;
END IF;
END IF;
END IP2RAW;
FUNCTION RAW2IP(ip IN RAW, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(45);
BEGIN
-- Range check "TO_NUMBER(ip, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') < 2**32, resp 2**128" not needed, because RAW values are usually not based on error-prone user input
IF ver = 4 THEN
-- Take only last 32 bit from RAW value with UTL_RAW.SUBSTR(ip, -4)
SELECT LISTAGG(TO_NUMBER(SUBSTR(SUBSTR(LPAD(RAWTOHEX(UTL_RAW.SUBSTR(ip, -4)), 8, '0'), -8), 2*LEVEL-1, 2), 'XX'), '.') WITHIN GROUP (ORDER BY LEVEL)
INTO res
FROM DUAL
CONNECT BY LEVEL <= 4;
RETURN res;
ELSIF ver = 6 THEN
RETURN Canonical_IPv6(SUBSTR(REGEXP_REPLACE(LPAD(RAWTOHEX(ip), 32, '0'), '([[:xdigit:]]{4})', ':\1'), 2));
ELSE
RAISE VALUE_ERROR;
END IF;
END RAW2IP;
FUNCTION Decimal2IP(ip IN NUMBER, ver IN INTEGER) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(45);
BEGIN
IF ip IS NULL THEN
RETURN NULL;
END IF;
IF ver = 4 THEN
IF ip > 2**32 - 1 THEN
RAISE NUMERIC_OVERFLOW;
END IF;
SELECT LISTAGG(TO_NUMBER(SUBSTR(LPAD(TO_CHAR(ip, 'fmXXXXXXXX'), 8, '0'), 2*LEVEL-1, 2), 'XX'), '.') WITHIN GROUP (ORDER BY LEVEL)
INTO res
FROM dual
CONNECT BY LEVEL <= 4;
RETURN res;
ELSIF ver = 6 THEN
IF ip > 2**128 - 1 THEN
RAISE NUMERIC_OVERFLOW;
END IF;
res := LPAD(TO_CHAR(ip, 'fmxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'),32, '0');
RETURN Canonical_IPv6(SUBSTR(REGEXP_REPLACE(res, '([[:xdigit:]]{4})', ':\1'), 2));
ELSE
RAISE VALUE_ERROR;
END IF;
END Decimal2IP;
FUNCTION SubnetMask(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
IF Ip IS NULL OR NOT REGEXP_LIKE(Ip, '/\d{1,3}$') THEN
RETURN NULL;
END IF;
IF REGEXP_LIKE(Ip, ':') THEN
RETURN CIDR2SubnetMask(REGEXP_SUBSTR(Ip, '\d{1,3}$'), 6);
ELSE
RETURN CIDR2SubnetMask(REGEXP_SUBSTR(Ip, '\d{1,2}$'), 4);
END IF;
END SubnetMask;
FUNCTION NetworkPrefix(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
RETURN RAW2IP(UTL_RAW.BIT_AND(Ip2RAW(Ip, 6), Ip2RAW(SubnetMask, 6)), 6);
ELSE
RETURN RAW2IP(UTL_RAW.BIT_AND(Ip2RAW(Ip, 4),Ip2RAW(SubnetMask, 4)), 4);
END IF;
END NetworkPrefix;
FUNCTION NetworkPrefix(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN NetworkPrefix(REGEXP_REPLACE(Ip, '/\d{1,3}$'), SubnetMask(Ip));
END NetworkPrefix;
FUNCTION BroadcastIp(Ip IN VARCHAR2, SubnetMask IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
Subnet RAW(16);
SubnetInv RAW(16);
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
Subnet := UTL_RAW.BIT_AND(Ip2RAW(Ip, 6), Ip2RAW(SubnetMask, 6));
SubnetInv := UTL_RAW.BIT_COMPLEMENT(Ip2RAW(SubnetMask, 6));
RETURN RAW2IP(UTL_RAW.BIT_OR(Subnet, SubnetInv), 6);
ELSE
Subnet := UTL_RAW.BIT_AND(Ip2RAW(Ip, 4), Ip2RAW(SubnetMask, 4));
SubnetInv := UTL_RAW.BIT_COMPLEMENT(Ip2RAW(SubnetMask, 4));
RETURN RAW2IP(UTL_RAW.BIT_OR(Subnet, SubnetInv), 4);
END IF;
END BroadcastIp;
FUNCTION BroadcastIp(Ip IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN BroadcastIp(REGEXP_REPLACE(Ip, '/\d{1,3}$'), SubnetMask(Ip));
END BroadcastIp;
FUNCTION SubnetMask2CIDR(SubnetMask VARCHAR2) RETURN INTEGER RESULT_CACHE DETERMINISTIC IS
ip RAW(16);
cidr INTEGER;
BEGIN
IF SubnetMask IS NULL THEN
RETURN NULL;
END IF;
IF REGEXP_LIKE(SubnetMask, ':') THEN
ip := IP2RAW(SubnetMask, 6);
cidr := 128-LOG(2, TO_NUMBER(UTL_RAW.BIT_COMPLEMENT(ip), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')+1);
ELSE
ip := IP2RAW(SubnetMask, 4);
cidr := 32-LOG(2, TO_NUMBER(UTL_RAW.BIT_COMPLEMENT(ip), 'XXXXXXXX')+1);
END IF;
RETURN cidr;
END SubnetMask2CIDR;
FUNCTION CIDR2SubnetMask(CIDR IN INTEGER, ver IN INTEGER) RETURN VARCHAR2 RESULT_CACHE DETERMINISTIC IS
BEGIN
IF CIDR IS NULL THEN
RETURN NULL;
END IF;
IF ver = 4 THEN
IF CIDR NOT BETWEEN 0 AND 32 THEN
RAISE VALUE_ERROR;
END IF;
RETURN RAW2IP(UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(32-cidr)-1, 'fmXXXXXXXX'),8 , '0'))), 4);
ELSIF ver = 6 THEN
IF CIDR NOT BETWEEN 0 AND 128 THEN
RAISE VALUE_ERROR;
END IF;
RETURN RAW2IP(UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(128-cidr)-1, 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))), 6);
ELSE
RAISE VALUE_ERROR;
END IF;
END CIDR2SubnetMask;
FUNCTION UncompressIpV6(Ip IN VARCHAR2, leadingZero IN BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 DETERMINISTIC IS
IpFull VARCHAR2(50);
len INTEGER := 7;
TYPE VARCHAR_TABLE_TYPE IS TABLE OF VARCHAR2(10);
BitFields VARCHAR_TABLE_TYPE;
cidr VARCHAR2(5);
BEGIN
IF NOT REGEXP_LIKE(Ip, ':') THEN
RETURN Ip;
END IF;
cidr := REGEXP_SUBSTR(Ip, '/\d{1,3}$');
IpFull := REGEXP_REPLACE(Ip, '/\d{1,3}$');
IF REGEXP_LIKE(IpFull, '::') THEN
IpFull := REGEXP_REPLACE(REGEXP_REPLACE(IpFull, '^::', '0::'), '::$', '::0');
IF REGEXP_LIKE(IpFull, ':\d+\.\d+\.\d+\.\d+') THEN
-- Mixed notation, e.g.: 2002::FFFF:129.144.52.38
len := 6;
END IF;
WHILE REGEXP_COUNT(IpFull, ':') <= len LOOP
IpFull := REGEXP_REPLACE(IpFull, '::', ':0::');
END LOOP;
IpFull := REGEXP_REPLACE(IpFull, '::', ':');
END IF;
IF NOT leadingZero THEN
RETURN LOWER(IpFull||cidr);
END IF;
SELECT REGEXP_SUBSTR(IpFull, '[^:]+', 1, LEVEL)
BULK COLLECT INTO BitFields
FROM dual
CONNECT BY REGEXP_SUBSTR(IpFull, '[^:]+', 1, LEVEL) IS NOT NULL;
IpFull := LPAD(BitFields(1), 4, '0');
FOR i IN 2..BitFields.COUNT LOOP
IF REGEXP_LIKE(BitFields(i), '\d+\.\d+\.\d+\.\d+') THEN
IpFull := IpFull ||':'||BitFields(i);
ELSE
IpFull := IpFull ||':'||LPAD(BitFields(i), 4, '0');
END IF;
END LOOP;
RETURN LOWER(IpFull)||cidr;
END UncompressIpV6;
FUNCTION Canonical_IPv6(IPv6 IN VARCHAR2) RETURN VARCHAR2 DETERMINISTIC IS
res VARCHAR2(50);
cidr VARCHAR2(5);
BEGIN
IF NOT REGEXP_LIKE(IPv6, ':') THEN
RETURN IPv6;
ELSIF REGEXP_LIKE(IPv6, '::') THEN
-- Do not shorten twice
res := UncompressIpV6(IPv6, FALSE);
ELSE
-- RFC 5952 section-4.3
res := LOWER(IPv6);
END IF;
-- Split CIDR if existing
cidr := REGEXP_SUBSTR(res, '/\d{1,3}$');
res := REGEXP_REPLACE(res, '/\d{1,3}$');
-- remove leading '0', RFC 5952 section-4.1
res := REGEXP_REPLACE(res, '(:|^)0+([[:xdigit:]]+)', '\1\2');
WITH ip AS
-- split IP into 16-bit fields
(SELECT REGEXP_SUBSTR(res, '[^:]+', 1, LEVEL) AS val, LEVEL AS pos
FROM DUAL
CONNECT BY REGEXP_SUBSTR(res, '[^:]+', 1, LEVEL) IS NOT NULL),
p AS
-- find consecutive (at least 2) 0 fields, RFC 5952 section-4.2.2
(SELECT pos, len, match_num
FROM ip
MATCH_RECOGNIZE (
ORDER BY pos
MEASURES
FINAL COUNT(*) AS len,
MATCH_NUMBER() AS match_num
ALL ROWS PER MATCH
PATTERN(zero{2,})
DEFINE zero AS val = '0')
),
m AS
-- select longest run of consecutive 0 fields, RFC 5952 section-4.2.3
(SELECT * FROM p WHERE len = (SELECT MAX(len) FROM p)),
f AS
-- select first sequence of longest run of consecutive 0 fields, RFC 5952 section-4.2.3
(SELECT * FROM m WHERE match_num = (SELECT MIN(match_num) FROM m))
SELECT REGEXP_REPLACE(LISTAGG(NVL2(match_num, ':', val), ':') WITHIN GROUP (ORDER BY pos), ':{2,}', '::')
INTO res
FROM ip
LEFT OUTER JOIN f USING (pos);
RETURN res||cidr;
END Canonical_IPv6;
END IP_Util;
/
Then you can use it for example like this:
SELECT
IP_Util.NetworkPrefix('2620:0:2d0:200::7/32'),
IP_Util.BroadcastIp('2620:0:2d0:200::7/32')
FROM dual;
2620:: 2620:0:ffff:ffff:ffff:ffff:ffff:ffff
If you prefer 2620:0:0:0:0:0:0:0 then use
IP_Util.UncompressIpV6(IP_Util.NetworkPrefix('2620:0:2d0:200::7/32'), false)
However, according RFC 5952 2620:: would be the preferred format.
Here are a few examples how this package can be used:
-- Determine if (IPv4) Address is a Private IP:
CREATE OR REPLACE FUNCTION IsPrivate_IP(ip IN VARCHAR2) RETURN NUMBER DETERMINISTIC IS
BEGIN
IF IP_Util.NetworkPrefix('10.0.0.0', '255.0.0.0') = IP_Util.NetworkPrefix(ip, '255.0.0.0') THEN
RETURN 1;
ELSIF IP_Util.NetworkPrefix('172.16.0.0', '255.240.0.0') = IP_Util.NetworkPrefix(ip, '255.240.0.0') THEN
RETURN 1;
ELSIF IP_Util.NetworkPrefix('192.168.0.0', '255.255.0.0') = IP_Util.NetworkPrefix(ip, '255.255.0.0') THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END IsPrivate_IP;
Or a more complex one which translates an IPv4 into IPv6 or vice versa using 6to4 and 6RD Network Prefix:
CREATE OR REPLACE FUNCTION NAT64(ip IN VARCHAR2, IpV6mask IN VARCHAR2 DEFAULT '::ffff:0:0') RETURN VARCHAR2 DETERMINISTIC IS
shift INTEGER;
cidr INTEGER;
n NUMBER;
a RAW(16);
b RAW(16);
BEGIN
IF REGEXP_LIKE(ip, ':') THEN
-- Translate from IPv6 to IPv4
IF NOT REGEXP_LIKE(IpV6mask, '/\d+{1,3}$') THEN
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_AND(IP_Util.IP2Raw(ip), HEXTORAW('000000000000000000000000FFFFFFFF')), 4);
ELSE
shift := 128 - REGEXP_SUBSTR(IpV6mask, '\d+{1,3}$');
IF shift < 32 THEN
RAISE VALUE_ERROR;
END IF;
-- Generate mask for IPv4 address, e.g. '0000000000000000FFFFFFFF00000000'
b := HEXTORAW(LPAD(TO_CHAR((2**shift-1) - (2**(shift-32)-1), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'));
n := TO_NUMBER(UTL_RAW.BIT_AND(IP_Util.IP2Raw(ip), b), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
-- UTL_RAW.SUBSTR does not work because you can shift only Bytes, I need Bits
RETURN IP_Util.Decimal2IP(TRUNC(n / 2**(shift-32)), 4);
END IF;
ELSE
-- Translate from IPv4 to IPv6
IF NOT REGEXP_LIKE(IpV6mask, '/\d+{1,3}$') THEN
a := UTL_RAW.BIT_AND(IP_Util.IP2Raw(IpV6mask), UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**32-1 , 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))));
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_OR(a, IP_Util.IP2RAW(ip, 6)), 6);
ELSE
cidr := REGEXP_SUBSTR(IpV6mask, '\d+{1,3}$');
shift := 128 - 32 - cidr;
IF shift < 0 THEN
RAISE VALUE_ERROR;
END IF;
a := UTL_RAW.BIT_AND(IP_Util.IP2Raw(IpV6mask), UTL_RAW.BIT_COMPLEMENT(HEXTORAW(LPAD(TO_CHAR(2**(128-cidr)-1 , 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'))));
b := HEXTORAW(LPAD(TO_CHAR(2**shift * IP_Util.IP2Decimal(ip), 'fmXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),32 , '0'));
RETURN IP_Util.RAW2IP(UTL_RAW.BIT_OR(a, b), 6);
END IF;
END IF;
END NAT64;