Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Za pomocą sql znajdź następną dostępną liczbę całkowitą w zakresie, który nie występuje w istniejących podzbiorach liczb całkowitych

W tym przypadku nie jest potrzebna rekursja, ponieważ mamy LEAD funkcja.

Pomyślę o problemie w kategoriach „luk” i „wysp”.

Na początku skupię się na IPv4, ponieważ łatwiej z nimi zrobić arytmetykę, ale pomysł na IPv6 jest taki sam, a na końcu pokażę ogólne rozwiązanie.

Na początek mamy pełną gamę możliwych adresów IP:od 0x00000000 do 0xFFFFFFFF .

Wewnątrz tego zakresu znajdują się „wyspy” zdefiniowane przez zakresy (włącznie) w dhcp_range :dhcp_range.begin_address, dhcp_range.end_address . Możesz myśleć o liście przypisanych adresów IP jako o innym zestawie wysp, z których każda ma po jednym elemencie:ip_address.address, ip_address.address . Wreszcie sama podsieć to dwie wyspy:0x00000000, subnet.ipv4_begin i subnet.ipv4_end, 0xFFFFFFFF .

Wiemy, że te wyspy nie nakładają się, co ułatwia nam życie. Wyspy mogą idealnie do siebie przylegać. Na przykład, gdy masz kilka kolejno przydzielonych adresów IP, przerwa między nimi wynosi zero. Wśród tych wszystkich wysp musimy znaleźć pierwszą przerwę, która ma przynajmniej jeden element, czyli niezerową przerwę, czyli kolejna wyspa zaczyna się o w pewnej odległości po zakończeniu poprzedniej wyspy.

Tak więc połączymy wszystkie wyspy za pomocą UNION (CTE_Islands ), a następnie przejrzyj je wszystkie w kolejności end_address (lub begin_address , użyj pola, które ma indeks) i użyj LEAD zerknąć przed siebie i uzyskać adres początkowy następnej wyspy. Na koniec będziemy mieli tabelę, w której każdy wiersz miał end_address aktualnej wyspy i begin_address następnej wyspy (CTE_Diff ). Jeśli różnica między nimi jest większa niż jeden, oznacza to, że „luka” jest wystarczająco duża i zwrócimy end_address obecnej wyspy plus 1.

Pierwszy dostępny adres IP dla danej podsieci

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;

Zestaw wyników zawierałby jeden wiersz, jeśli dostępny jest co najmniej jeden adres IP i nie zawierałby żadnych wierszy, jeśli nie ma dostępnych adresów IP.

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

Oto link do SQLFiddle . Nie działało z parametrem, więc na stałe zakodowałem 1 tam. Zmień go w UNION na inny identyfikator podsieci (2 lub 3), aby wypróbować inne podsieci. Ponadto nie wyświetlał wyniku w varbinary poprawnie, więc zostawiłem to jako bigint. Użyj, powiedzmy, kalkulatora Windows, aby przekonwertować go na szesnastkowy, aby zweryfikować wynik.

Jeśli nie ograniczysz wyników do pierwszej przerwy według TOP(1) , otrzymasz listę wszystkich dostępnych zakresów adresów IP (luki).

Lista wszystkich zakresów dostępnych adresów IP dla danej podsieci

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;

Wynik. Skrzypce SQL z wynikiem w postaci prostego bigint, nie w postaci szesnastkowej i z zakodowanym na stałe identyfikatorem parametru.

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

Pierwszy dostępny adres IP dla każdej podsieci

Łatwo jest rozszerzyć zapytanie i zwrócić pierwszy dostępny adres IP dla wszystkich podsieci, zamiast określać jedną konkretną podsieć. Użyj CROSS APPLY aby uzyskać listę wysp dla każdej podsieci, a następnie dodać PARTITION BY subnet_sk w LEAD funkcja.

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

Zestaw wyników

subnet_sk    NextAvailableIPAddress
1            0xAC101129
2            0xC0A81B1F
3            0xC0A8160C

Oto SQLFiddle . Musiałem usunąć konwersję do varbinary w SQL Fiddle, ponieważ niepoprawnie wyświetlał wyniki.

Ogólne rozwiązanie dla IPv4 i IPv6

Wszystkie zakresy dostępnych adresów IP dla wszystkich podsieci

SQL Fiddle z przykładowymi danymi IPv4 i IPv6, funkcjami i końcowym zapytaniem

Twoje przykładowe dane dla IPv6 nie były całkiem poprawne — koniec podsieci 0xFC00000000000000FFFFFFFFFFFFFFFF był mniejszy niż zakresy dhcp, więc zmieniłem to na 0xFC0001066800000000000000FFFFFFFF . Ponadto w tej samej podsieci znajdowały się zarówno IPv4, jak i IPv6, co jest kłopotliwe w obsłudze. Na potrzeby tego przykładu zmieniłem nieco twój schemat - zamiast jawnego ipv4_begin / end i ipv6_begin / end w subnet Zrobiłem to po prostu ip_begin / end jako varbinary(16) (tak samo jak w przypadku innych stołów). Usunąłem też address_family , w przeciwnym razie był zbyt duży dla SQL Fiddle.

Funkcje arytmetyczne

Aby to działało dla IPv6, musimy dowiedzieć się, jak dodać/odjąć 1 do/z binary(16) . Zrobiłbym dla tego funkcję CLR. Jeśli nie możesz włączyć CLR, jest to możliwe za pośrednictwem standardowego T-SQL. Stworzyłem dwie funkcje, które zwracają tabelę, a nie skalar, ponieważ w ten sposób mogą być one wbudowane przez optymalizator. Chciałem stworzyć ogólne rozwiązanie, aby funkcja akceptowała varbinary(16) i działa zarówno dla IPv4, jak i IPv6.

Oto funkcja T-SQL do inkrementacji varbinary(16) o jeden. Jeśli parametr nie ma długości 16 bajtów, zakładam, że jest to IPv4 i po prostu konwertuję go na bigint dodać 1 a potem z powrotem do binary . W przeciwnym razie dzielę binary(16) na dwie części o długości 8 bajtów każda i rzutuj je na bigint . bigint jest podpisany, ale potrzebujemy przyrostu bez znaku, więc musimy sprawdzić kilka przypadków.

else część jest najczęstsza - po prostu zwiększamy niską część o jeden i dołączamy wynik do oryginalnej wysokiej części.

Jeśli dolna część to 0xFFFFFFFFFFFFFFFF , następnie ustawiamy dolną część na 0x0000000000000000 i przenieś flagę, tj. zwiększ wysoką część o jeden.

Jeśli dolna część to 0x7FFFFFFFFFFFFFFF , następnie ustawiamy dolną część na 0x8000000000000000 jawnie, ponieważ próba zwiększenia tego bigint wartość spowodowałaby przepełnienie.

Jeśli liczba całkowita to 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ustawiamy wynik na 0x00000000000000000000000000000000 .

Funkcja zmniejszania o jeden jest podobna.

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

Wszystkie zakresy dostępnych adresów IP dla wszystkich podsieci

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;

Zestaw wyników

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

Plany wykonania

Byłem ciekaw, jak działają sugerowane tutaj różne rozwiązania, więc przyjrzałem się ich planom wykonawczym. Pamiętaj, że te plany dotyczą małego przykładowego zestawu danych bez żadnych indeksów.

Moje ogólne rozwiązanie dla IPv4 i IPv6:

Podobne rozwiązanie autorstwa dnoeth :

Rozwiązanie autorstwa cha który nie używa LEAD funkcja:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. sql pivot z dynamicznymi kolumnami

  2. Automatyzacja zadań tworzenia kopii zapasowych i konserwacji za pomocą planu konserwacji w SQL Server

  3. SQL:Co jest lepsze Bit czy char(1)

  4. Unikalny złożony klucz składający się z dwóch pól w programie SQL Server z automatycznym przyrostem drugiego pola

  5. 4 sposoby na uzyskanie historii zadań SQL Server