Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Mit SQL finden Sie die nächste verfügbare Ganzzahl innerhalb eines Bereichs, der in der/den vorhandenen Ganzzahl-Teilmenge(n) nicht vorhanden ist

In diesem Fall ist keine Rekursion erforderlich, da wir LEAD haben Funktion.

Ich werde das Problem in Begriffen von "Lücken" und "Inseln" betrachten.

Ich werde mich zunächst auf IPv4 konzentrieren, weil es einfacher ist, damit zu rechnen, aber die Idee für IPv6 ist die gleiche und am Ende werde ich eine generische Lösung zeigen.

Zunächst haben wir eine vollständige Palette möglicher IPs:von 0x00000000 zu 0xFFFFFFFF .

Innerhalb dieses Bereichs gibt es „Inseln“, die durch die Bereiche (einschließlich) in dhcp_range definiert sind :dhcp_range.begin_address, dhcp_range.end_address . Sie können sich die Liste der zugewiesenen IP-Adressen als eine weitere Gruppe von Inseln vorstellen, die jeweils ein Element haben:ip_address.address, ip_address.address . Schließlich besteht das Subnetz selbst aus zwei Inseln:0x00000000, subnet.ipv4_begin und subnet.ipv4_end, 0xFFFFFFFF .

Wir wissen, dass diese Inseln nicht überlappen, was unser Leben leichter macht. Inseln können perfekt nebeneinander liegen. Wenn Sie beispielsweise wenige aufeinanderfolgend zugewiesene IP-Adressen haben, ist die Lücke zwischen ihnen null. Unter all diesen Inseln müssen wir die erste Lücke finden, die mindestens ein Element enthält, d. h. eine Lücke ungleich null, d. h. die nächste Insel beginnt bei einige Entfernung nach der vorherigen Insel endet.

Also werden wir alle Inseln mit UNION zusammenfügen (CTE_Islands ) und gehen Sie dann alle in der Reihenfolge end_address durch (oder begin_address , verwenden Sie das Feld mit Index) und verwenden Sie LEAD um nach vorne zu schauen und die Startadresse der nächsten Insel zu erhalten. Am Ende haben wir eine Tabelle, in der jede Zeile end_address hat der aktuellen Insel und begin_address der nächsten Insel (CTE_Diff ). Wenn der Unterschied zwischen ihnen mehr als eins beträgt, bedeutet dies, dass die „Lücke“ groß genug ist und wir die end_address zurückgeben der aktuellen Insel plus 1.

Die erste verfügbare IP-Adresse für das angegebene Subnetz

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;

Die Ergebnismenge enthält eine Zeile, wenn mindestens eine IP-Adresse verfügbar ist, und enthält überhaupt keine Zeilen, wenn keine IP-Adresse verfügbar ist.

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

Hier ist ein Link zu SQLFiddle . Es funktionierte nicht mit Parametern, also habe ich 1 fest codiert dort. Ändern Sie es in UNION in eine andere Subnetz-ID (2 oder 3), um andere Subnetze auszuprobieren. Außerdem wurde das Ergebnis nicht in varbinary angezeigt richtig, also habe ich es als bigint belassen. Verwenden Sie beispielsweise den Windows-Rechner, um es in Hex umzuwandeln, um das Ergebnis zu überprüfen.

Wenn Sie die Ergebnisse nicht auf die erste Lücke von TOP(1) beschränken , erhalten Sie eine Liste aller verfügbaren IP-Bereiche (Lücken).

Liste aller Bereiche verfügbarer IP-Adressen für ein bestimmtes Subnetz

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;

Ergebnis. SQL-Geige mit Ergebnis als einfaches Bigint, nicht in Hex und mit fest codierter 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

Die erste verfügbare IP-Adresse für jedes Subnetz

Es ist einfach, die Abfrage zu erweitern und die erste verfügbare IP-Adresse für alle Subnetze zurückzugeben, anstatt ein bestimmtes Subnetz anzugeben. Verwenden Sie CROSS APPLY um eine Liste der Inseln für jedes Subnetz zu erhalten, und fügen Sie dann PARTITION BY subnet_sk hinzu in den LEAD Funktion.

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

Ergebnissatz

subnet_sk    NextAvailableIPAddress
1            0xAC101129
2            0xC0A81B1F
3            0xC0A8160C

Hier ist SQLFiddle . Ich musste die Konvertierung in varbinary entfernen in SQL Fiddle, weil die Ergebnisse falsch angezeigt wurden.

Generische Lösung für IPv4 und IPv6

Alle Bereiche verfügbarer IP-Adressen für alle Subnetze

SQL Fiddle mit IPv4- und IPv6-Beispieldaten, Funktionen und abschließender Abfrage

Ihre Beispieldaten für IPv6 waren nicht ganz korrekt - das Ende des Subnetzes 0xFC00000000000000FFFFFFFFFFFFFFFF war kleiner als Ihre DHCP-Bereiche, also habe ich das in 0xFC0001066800000000000000FFFFFFFF geändert . Außerdem hatten Sie sowohl IPv4 als auch IPv6 im selben Subnetz, was umständlich zu handhaben ist. Für dieses Beispiel habe ich Ihr Schema ein wenig geändert - anstatt explizit ipv4_begin / end zu haben und ipv6_begin / end im subnet Ich habe es einfach zu ip_begin / end gemacht als varbinary(16) (dasselbe wie für Ihre anderen Tabellen). Ich habe auch address_family entfernt , sonst wäre es zu groß für SQL Fiddle.

Arithmetische Funktionen

Damit es für IPv6 funktioniert, müssen wir herausfinden, wie man 1 addiert/subtrahiert zu/von binary(16) . Ich würde CLR-Funktion dafür machen. Wenn Sie CLR nicht aktivieren dürfen, ist dies über Standard-T-SQL möglich. Ich habe zwei Funktionen erstellt, die eher eine Tabelle als einen Skalar zurückgeben, da sie auf diese Weise vom Optimierer eingebunden werden können. Ich wollte eine generische Lösung erstellen, damit die Funktion varbinary(16) akzeptiert und funktionieren sowohl für IPv4 als auch für IPv6.

Hier ist die T-SQL-Funktion zum Erhöhen von varbinary(16) einzeln. Wenn der Parameter nicht 16 Byte lang ist, gehe ich davon aus, dass es sich um IPv4 handelt, und wandle ihn einfach in bigint um um 1 hinzuzufügen und dann zurück zu binary . Andernfalls spalte ich binary(16) auf in zwei Teile mit je 8 Byte Länge und gieße sie in bigint . bigint ist signiert, aber wir benötigen ein nicht signiertes Inkrement, daher müssen wir einige Fälle überprüfen.

Das else Teil ist am häufigsten - wir erhöhen einfach den niedrigen Teil um eins und hängen das Ergebnis an den ursprünglichen hohen Teil an.

Wenn der niedrige Teil 0xFFFFFFFFFFFFFFFF ist , dann setzen wir den niedrigen Teil auf 0x0000000000000000 und das Flag übernehmen, d.h. den hohen Teil um eins erhöhen.

Wenn der niedrige Teil 0x7FFFFFFFFFFFFFFF ist , dann setzen wir den niedrigen Teil auf 0x8000000000000000 explizit, weil ein Versuch, diesen bigint zu inkrementieren Wert würde einen Überlauf verursachen.

Wenn die ganze Zahl 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ist Wir setzen das Ergebnis auf 0x00000000000000000000000000000000 .

Die Funktion zum Dekrementieren um eins ist ähnlich.

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 Bereiche verfügbarer IP-Adressen für alle Subnetze

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;

Ergebnissatz

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

Ausführungspläne

Ich war neugierig zu sehen, wie die verschiedenen hier vorgeschlagenen Lösungen funktionieren, also habe ich mir ihre Ausführungspläne angesehen. Beachten Sie, dass diese Pläne für den kleinen Beispieldatensatz ohne Indizes gelten.

Meine generische Lösung für IPv4 und IPv6:

Ähnliche Lösung von dnoeth :

Lösung von cha das LEAD nicht verwendet Funktion: