sql >> Database >  >> RDS >> Sqlserver

Zoek met sql het volgende beschikbare gehele getal binnen het bereik dat niet aanwezig is in de bestaande gehele deelverzameling(en)

In dit geval is geen recursie nodig, omdat we LEAD . hebben functie.

Ik zal over het probleem nadenken in termen van "gaten" en "eilanden".

Ik zal me eerst concentreren op IPv4, omdat het gemakkelijker is om ermee te rekenen, maar het idee voor IPv6 is hetzelfde en uiteindelijk zal ik een generieke oplossing laten zien.

Om te beginnen hebben we een volledige reeks mogelijke IP's:van 0x00000000 naar 0xFFFFFFFF .

Binnen dit bereik zijn er "eilanden" gedefinieerd door de bereiken (inclusief) in dhcp_range :dhcp_range.begin_address, dhcp_range.end_address . U kunt de lijst met toegewezen IP-adressen zien als een andere set eilanden, die elk één element hebben:ip_address.address, ip_address.address . Ten slotte bestaat het subnet zelf uit twee eilanden:0x00000000, subnet.ipv4_begin en subnet.ipv4_end, 0xFFFFFFFF .

We weten dat deze eilanden niet overlappen, wat ons leven gemakkelijker maakt. Eilanden kunnen perfect aan elkaar grenzen. Als u bijvoorbeeld weinig achtereenvolgens toegewezen IP-adressen heeft, is de kloof ertussen nul. Van al deze eilanden moeten we de eerste kloof vinden, die ten minste één element heeft, d.w.z. niet-nul kloof, d.w.z. het volgende eiland begint bij enige afstand nadat het vorige eiland eindigt.

Dus we zullen alle eilanden samenvoegen met behulp van UNION (CTE_Islands ) en doorloop ze vervolgens allemaal in de volgorde end_address (of begin_address , gebruik het veld met index erop) en gebruik LEAD om vooruit te kijken en het startadres van het volgende eiland te krijgen. Op het einde hebben we een tabel, waar elke rij end_address . had van het huidige eiland en begin_address van het volgende eiland (CTE_Diff ). Als het verschil tussen beide meer dan één is, betekent dit dat de "gap" groot genoeg is en dat we het end_address teruggeven van het huidige eiland plus 1.

Het eerste beschikbare IP-adres voor het opgegeven subnet

DECLARE @ParamSubnet_sk int = 1;

WITH
CTE_Islands
AS
(
    SELECT CAST(begin_address AS bigint) AS begin_address, CAST(end_address AS bigint) AS end_address
    FROM dhcp_range
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(address AS bigint) AS begin_address, CAST(address AS bigint) AS end_address
    FROM ip_address
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(0x00000000 AS bigint) AS begin_address, CAST(ipv4_begin AS bigint) AS end_address
    FROM subnet
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(ipv4_end AS bigint) AS begin_address, CAST(0xFFFFFFFF AS bigint) AS end_address
    FROM subnet
    WHERE subnet_sk = @ParamSubnet_sk
)
,CTE_Diff
AS
(
    SELECT
        begin_address
        , end_address
        --, LEAD(begin_address) OVER(ORDER BY end_address) AS BeginNextIsland
        , LEAD(begin_address) OVER(ORDER BY end_address) - end_address AS Diff
    FROM CTE_Islands
)
SELECT TOP(1)
    CAST(end_address + 1 AS varbinary(4)) AS NextAvailableIPAddress
FROM CTE_Diff
WHERE Diff > 1
ORDER BY end_address;

De resultatenset zou één rij bevatten als er ten minste één IP-adres beschikbaar is en zou helemaal geen rijen bevatten als er geen IP-adressen beschikbaar zijn.

For parameter 1 result is `0xAC101129`.
For parameter 2 result is `0xC0A81B1F`.
For parameter 3 result is `0xC0A8160C`.

Hier is een link naar SQLFiddle . Het werkte niet met parameter, dus ik heb 1 hard gecodeerd daar. Verander het in UNION in een ander subnet-ID (2 of 3) om andere subnetten te proberen. Het gaf ook geen resultaat weer in varbinary correct, dus ik liet het als bigint. Gebruik bijvoorbeeld de Windows-rekenmachine om het naar hex te converteren om het resultaat te verifiëren.

Als u de resultaten niet beperkt tot het eerste gat met TOP(1) , krijgt u een lijst met alle beschikbare IP-bereiken (hiaten).

Lijst met alle reeksen van beschikbare IP-adressen voor een bepaald subnet

DECLARE @ParamSubnet_sk int = 1;

WITH
CTE_Islands
AS
(
    SELECT CAST(begin_address AS bigint) AS begin_address, CAST(end_address AS bigint) AS end_address
    FROM dhcp_range
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(address AS bigint) AS begin_address, CAST(address AS bigint) AS end_address
    FROM ip_address
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(0x00000000 AS bigint) AS begin_address, CAST(ipv4_begin AS bigint) AS end_address
    FROM subnet
    WHERE subnet_sk = @ParamSubnet_sk

    UNION ALL

    SELECT CAST(ipv4_end AS bigint) AS begin_address, CAST(0xFFFFFFFF AS bigint) AS end_address
    FROM subnet
    WHERE subnet_sk = @ParamSubnet_sk
)
,CTE_Diff
AS
(
    SELECT
        begin_address
        , end_address
        , LEAD(begin_address) OVER(ORDER BY end_address) AS BeginNextIsland
        , LEAD(begin_address) OVER(ORDER BY end_address) - end_address AS Diff
    FROM CTE_Islands
)
SELECT
    CAST(end_address + 1 AS varbinary(4)) AS begin_range_AvailableIPAddress
    ,CAST(BeginNextIsland - 1 AS varbinary(4)) AS end_range_AvailableIPAddress
FROM CTE_Diff
WHERE Diff > 1
ORDER BY end_address;

Resultaat. SQL Fiddle met resultaat als eenvoudige bigint, niet in hex, en met hardgecodeerde parameter-ID.

Result set for ID = 1
begin_range_AvailableIPAddress    end_range_AvailableIPAddress
0xAC101129                        0xAC10112E

Result set for ID = 2
begin_range_AvailableIPAddress    end_range_AvailableIPAddress
0xC0A81B1F                        0xC0A81B1F
0xC0A81B22                        0xC0A81B28
0xC0A81BFA                        0xC0A81BFE

Result set for ID = 3
begin_range_AvailableIPAddress    end_range_AvailableIPAddress
0xC0A8160C                        0xC0A8160C
0xC0A816FE                        0xC0A816FE

Het eerste beschikbare IP-adres voor elk subnet

Het is eenvoudig om de query uit te breiden en het eerste beschikbare IP-adres voor alle subnetten te retourneren, in plaats van één bepaald subnet op te geven. Gebruik CROSS APPLY om een ​​lijst met eilanden voor elk subnet te krijgen en vervolgens PARTITION BY subnet_sk . toe te voegen in de LEAD functie.

WITH
CTE_Islands
AS
(
    SELECT
        subnet_sk
        , begin_address
        , end_address
    FROM
        subnet AS Main
        CROSS APPLY
        (
            SELECT CAST(begin_address AS bigint) AS begin_address, CAST(end_address AS bigint) AS end_address
            FROM dhcp_range
            WHERE dhcp_range.subnet_sk = Main.subnet_sk

            UNION ALL

            SELECT CAST(address AS bigint) AS begin_address, CAST(address AS bigint) AS end_address
            FROM ip_address
            WHERE ip_address.subnet_sk = Main.subnet_sk

            UNION ALL

            SELECT CAST(0x00000000 AS bigint) AS begin_address, CAST(ipv4_begin AS bigint) AS end_address
            FROM subnet
            WHERE subnet.subnet_sk = Main.subnet_sk

            UNION ALL

            SELECT CAST(ipv4_end AS bigint) AS begin_address, CAST(0xFFFFFFFF AS bigint) AS end_address
            FROM subnet
            WHERE subnet.subnet_sk = Main.subnet_sk
        ) AS CA
)
,CTE_Diff
AS
(
    SELECT
        subnet_sk
        , begin_address
        , end_address
        , LEAD(begin_address) OVER(PARTITION BY subnet_sk ORDER BY end_address) - end_address AS Diff
    FROM CTE_Islands
)
SELECT
    subnet_sk
    , CAST(MIN(end_address) + 1 as varbinary(4)) AS NextAvailableIPAddress
FROM CTE_Diff
WHERE Diff > 1
GROUP BY subnet_sk

Resultatenset

subnet_sk    NextAvailableIPAddress
1            0xAC101129
2            0xC0A81B1F
3            0xC0A8160C

Hier is SQLFiddle . Ik moest de conversie naar varbinary verwijderen in SQL Fiddle, omdat het de resultaten onjuist liet zien.

Algemene oplossing voor zowel IPv4 als IPv6

Alle reeksen van beschikbare IP-adressen voor alle subnetten

SQL Fiddle met voorbeeld-IPv4- en IPv6-gegevens, functies en laatste vraag

Uw voorbeeldgegevens voor IPv6 waren niet helemaal correct - het einde van het subnet 0xFC00000000000000FFFFFFFFFFFFFFFF was kleiner dan je dhcp-bereiken, dus ik heb dat gewijzigd in 0xFC0001066800000000000000FFFFFFFF . Bovendien had je zowel IPv4 als IPv6 in hetzelfde subnet, wat lastig te hanteren is. Omwille van dit voorbeeld heb ik je schema een beetje veranderd - in plaats van expliciete ipv4_begin / end en ipv6_begin / end in subnet Ik heb het gewoon ip_begin / end . gemaakt als varbinary(16) (hetzelfde als voor uw andere tabellen). Ik heb ook address_family verwijderd , anders was het te groot voor SQL Fiddle.

Rekenkundige functies

Om het voor IPv6 te laten werken, moeten we uitzoeken hoe we 1 kunnen optellen/aftrekken naar/van binary(16) . Ik zou ervoor zorgen dat CLR werkt. Als je CLR niet mag inschakelen, kan dat via standaard T-SQL. Ik heb twee functies gemaakt die een tabel retourneren in plaats van scalair, omdat ze op zo'n manier door de optimizer kunnen worden inline. Ik wilde een generieke oplossing maken, zodat de functie varbinary(16) . zou accepteren en werken voor zowel IPv4 als IPv6.

Hier is de T-SQL-functie om varbinary(16) te verhogen bij een. Als de parameter niet 16 bytes lang is, neem ik aan dat het IPv4 is en converteer ik deze eenvoudig naar bigint om 1 toe te voegen en dan terug naar binary . Anders split ik binary(16) in twee delen van elk 8 bytes lang en giet ze in bigint . bigint is ondertekend, maar we hebben een niet-ondertekend increment nodig, dus we moeten enkele gevallen controleren.

De else deel komt het meest voor - we verhogen het lage deel met één en voegen het resultaat toe aan het originele hoge deel.

Als het lage deel 0xFFFFFFFFFFFFFFFF is , dan stellen we het lage deel in op 0x0000000000000000 en de vlag overdragen, d.w.z. het hoge gedeelte met één verhogen.

Als het lage deel 0x7FFFFFFFFFFFFFFF is , dan stellen we het lage deel in op 0x8000000000000000 expliciet, omdat een poging om deze bigint waarde zou overflow veroorzaken.

Als het hele getal 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF is we zetten resultaat op 0x00000000000000000000000000000000 .

De functie om met één te verlagen is vergelijkbaar.

CREATE FUNCTION [dbo].[BinaryInc](@src varbinary(16))
RETURNS TABLE AS
RETURN
    SELECT
    CASE WHEN DATALENGTH(@src) = 16
    THEN
        -- Increment IPv6 by splitting it into two bigints 8 bytes each and then concatenating them
        CASE
        WHEN @src = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
        THEN 0x00000000000000000000000000000000

        WHEN SUBSTRING(@src, 9, 8) = 0x7FFFFFFFFFFFFFFF
        THEN SUBSTRING(@src, 1, 8) + 0x8000000000000000

        WHEN SUBSTRING(@src, 9, 8) = 0xFFFFFFFFFFFFFFFF
        THEN CAST(CAST(SUBSTRING(@src, 1, 8) AS bigint) + 1 AS binary(8)) + 0x0000000000000000

        ELSE SUBSTRING(@src, 1, 8) + CAST(CAST(SUBSTRING(@src, 9, 8) AS bigint) + 1 AS binary(8))
        END
    ELSE
        -- Increment IPv4 by converting it into 8 byte bigint and then back into 4 bytes binary
        CAST(CAST(CAST(@src AS bigint) + 1 AS binary(4)) AS varbinary(16))
    END AS Result
    ;
GO

CREATE FUNCTION [dbo].[BinaryDec](@src varbinary(16))
RETURNS TABLE AS
RETURN
    SELECT
    CASE WHEN DATALENGTH(@src) = 16
    THEN
        -- Decrement IPv6 by splitting it into two bigints 8 bytes each and then concatenating them
        CASE
        WHEN @src = 0x00000000000000000000000000000000
        THEN 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

        WHEN SUBSTRING(@src, 9, 8) = 0x8000000000000000
        THEN SUBSTRING(@src, 1, 8) + 0x7FFFFFFFFFFFFFFF

        WHEN SUBSTRING(@src, 9, 8) = 0x0000000000000000
        THEN CAST(CAST(SUBSTRING(@src, 1, 8) AS bigint) - 1 AS binary(8)) + 0xFFFFFFFFFFFFFFFF

        ELSE SUBSTRING(@src, 1, 8) + CAST(CAST(SUBSTRING(@src, 9, 8) AS bigint) - 1 AS binary(8))
        END
    ELSE
        -- Decrement IPv4 by converting it into 8 byte bigint and then back into 4 bytes binary
        CAST(CAST(CAST(@src AS bigint) - 1 AS binary(4)) AS varbinary(16))
    END AS Result
    ;
GO

Alle reeksen van beschikbare IP-adressen voor alle subnetten

WITH
CTE_Islands
AS
(
    SELECT subnet_sk, begin_address, end_address
    FROM dhcp_range

    UNION ALL

    SELECT subnet_sk, address AS begin_address, address AS end_address
    FROM ip_address

    UNION ALL

    SELECT subnet_sk, SUBSTRING(0x00000000000000000000000000000000, 1, DATALENGTH(ip_begin)) AS begin_address, ip_begin AS end_address
    FROM subnet

    UNION ALL

    SELECT subnet_sk, ip_end AS begin_address, SUBSTRING(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1, DATALENGTH(ip_end)) AS end_address
    FROM subnet
)
,CTE_Gaps
AS
(
    SELECT
        subnet_sk
        ,end_address AS EndThisIsland
        ,LEAD(begin_address) OVER(PARTITION BY subnet_sk ORDER BY end_address) AS BeginNextIsland
    FROM CTE_Islands
)
,CTE_GapsIncDec
AS
(
    SELECT
        subnet_sk
        ,EndThisIsland
        ,EndThisIslandInc
        ,BeginNextIslandDec
        ,BeginNextIsland
    FROM CTE_Gaps
        CROSS APPLY
        (
            SELECT bi.Result AS EndThisIslandInc
            FROM dbo.BinaryInc(EndThisIsland) AS bi
        ) AS CA_Inc
        CROSS APPLY
        (
            SELECT bd.Result AS BeginNextIslandDec
            FROM dbo.BinaryDec(BeginNextIsland) AS bd
        ) AS CA_Dec
)
SELECT
    subnet_sk
    ,EndThisIslandInc AS begin_range_AvailableIPAddress
    ,BeginNextIslandDec AS end_range_AvailableIPAddress
FROM CTE_GapsIncDec
WHERE CTE_GapsIncDec.EndThisIslandInc <> BeginNextIsland
ORDER BY subnet_sk, EndThisIsland;

Resultatenset

subnet_sk    begin_range_AvailableIPAddress        end_range_AvailableIPAddress
1            0xAC101129                            0xAC10112E
2            0xC0A81B1F                            0xC0A81B1F
2            0xC0A81B22                            0xC0A81B28
2            0xC0A81BFA                            0xC0A81BFE
3            0xC0A8160C                            0xC0A8160C
3            0xC0A816FE                            0xC0A816FE
4            0xFC000000000000000000000000000001    0xFC0000000000000000000000000000FF
4            0xFC000000000000000000000000000101    0xFC0000000000000000000000000001FF
4            0xFC000000000000000000000000000201    0xFC0000000000000000000000000002FF
4            0xFC000000000000000000000000000301    0xFC0000000000000000000000000003FF
4            0xFC000000000000000000000000000401    0xFC0000000000000000000000000004FF
4            0xFC000000000000000000000000000501    0xFC0000000000000000000000000005FF
4            0xFC000000000000000000000000000601    0xFC0000000000000000000000000006FF
4            0xFC000000000000000000000000000701    0xFC0000000000000000000000000007FF
4            0xFC000000000000000000000000000801    0xFC0000000000000000000000000008FF
4            0xFC000000000000000000000000000901    0xFC00000000000000BFFFFFFFFFFFFFFD
4            0xFC00000000000000BFFFFFFFFFFFFFFF    0xFC00000000000000CFFFFFFFFFFFFFFD
4            0xFC00000000000000CFFFFFFFFFFFFFFF    0xFC00000000000000FBFFFFFFFFFFFFFD
4            0xFC00000000000000FBFFFFFFFFFFFFFF    0xFC00000000000000FCFFFFFFFFFFFFFD
4            0xFC00000000000000FCFFFFFFFFFFFFFF    0xFC00000000000000FFBFFFFFFFFFFFFD
4            0xFC00000000000000FFBFFFFFFFFFFFFF    0xFC00000000000000FFCFFFFFFFFFFFFD
4            0xFC00000000000000FFCFFFFFFFFFFFFF    0xFC00000000000000FFFBFFFFFFFFFFFD
4            0xFC00000000000000FFFBFFFFFFFFFFFF    0xFC00000000000000FFFCFFFFFFFFFFFD
4            0xFC00000000000000FFFCFFFFFFFFFFFF    0xFC00000000000000FFFFBFFFFFFFFFFD
4            0xFC00000000000000FFFFBFFFFFFFFFFF    0xFC00000000000000FFFFCFFFFFFFFFFD
4            0xFC00000000000000FFFFCFFFFFFFFFFF    0xFC00000000000000FFFFFBFFFFFFFFFD
4            0xFC00000000000000FFFFFBFFFFFFFFFF    0xFC00000000000000FFFFFCFFFFFFFFFD
4            0xFC00000000000000FFFFFCFFFFFFFFFF    0xFC00000000000000FFFFFFBFFFFFFFFD
4            0xFC00000000000000FFFFFFBFFFFFFFFF    0xFC00000000000000FFFFFFCFFFFFFFFD
4            0xFC00000000000000FFFFFFCFFFFFFFFF    0xFC00000000000000FFFFFFFBFFFFFFFD
4            0xFC00000000000000FFFFFFFBFFFFFFFF    0xFC00000000000000FFFFFFFCFFFFFFFD
4            0xFC00000000000000FFFFFFFCFFFFFFFF    0xFC00000000000000FFFFFFFFBFFFFFFD
4            0xFC00000000000000FFFFFFFFBFFFFFFF    0xFC00000000000000FFFFFFFFCFFFFFFD
4            0xFC00000000000000FFFFFFFFCFFFFFFF    0xFC00000000000000FFFFFFFFFBFFFFFD
4            0xFC00000000000000FFFFFFFFFBFFFFFF    0xFC00000000000000FFFFFFFFFCFFFFFD
4            0xFC00000000000000FFFFFFFFFCFFFFFF    0xFC00000000000000FFFFFFFFFFBFFFFD
4            0xFC00000000000000FFFFFFFFFFBFFFFF    0xFC00000000000000FFFFFFFFFFCFFFFD
4            0xFC00000000000000FFFFFFFFFFCFFFFF    0xFC00000000000000FFFFFFFFFFFBFFFD
4            0xFC00000000000000FFFFFFFFFFFBFFFF    0xFC00000000000000FFFFFFFFFFFCFFFD
4            0xFC00000000000000FFFFFFFFFFFCFFFF    0xFC00000000000000FFFFFFFFFFFFBFFD
4            0xFC00000000000000FFFFFFFFFFFFBFFF    0xFC00000000000000FFFFFFFFFFFFCFFD
4            0xFC00000000000000FFFFFFFFFFFFCFFF    0xFC00000000000000FFFFFFFFFFFFFBFD
4            0xFC00000000000000FFFFFFFFFFFFFBFF    0xFC00000000000000FFFFFFFFFFFFFCFD
4            0xFC00000000000000FFFFFFFFFFFFFCFF    0xFC00000000000000FFFFFFFFFFFFFFBD
4            0xFC00000000000000FFFFFFFFFFFFFFBF    0xFC00000000000000FFFFFFFFFFFFFFCD
4            0xFC00000000000000FFFFFFFFFFFFFFCF    0xFC0001065FFFFFFFFFFFFFFFFFFFFFFF
4            0xFC000106600000000000000100000000    0xFC00010666FFFFFFFFFFFFFFFFFFFFFF
4            0xFC000106670000000000000100000000    0xFC000106677FFFFFFFFFFFFFFFFFFFFF
4            0xFC000106678000000000000100000000    0xFC000106678FFFFFFFFFFFFFFFFFFFFF
4            0xFC000106679000000000000100000000    0xFC0001066800000000000000FFFFFFFE

Uitvoeringsplannen

Ik was nieuwsgierig om te zien hoe de verschillende oplossingen die hier worden voorgesteld werken, dus ik keek naar hun uitvoeringsplannen. Houd er rekening mee dat deze abonnementen bedoeld zijn voor de kleine steekproefset met gegevens zonder indexen.

Mijn generieke oplossing voor zowel IPv4 als IPv6:

Vergelijkbare oplossing door dnoeth :

Oplossing door cha die geen gebruik maakt van LEAD functie:



  1. SOM van MySQL voor elke n record

  2. Foreign Key toevoegen mislukt in MySQL met foutcode 1005, nummer 150

  3. MS Access:voor- en nadelen

  4. MySQL LIKE werkt niet met â