sql >> データベース >  >> RDS >> Sqlserver

SQLを使用して、既存の整数サブセットに存在しない範囲内で次に利用可能な整数を見つけます

    この場合、LEAD があるため、再帰は必要ありません。 関数。

    「ギャップ」と「島」の観点から問題を考えてみます。

    最初は IPv4 に焦点を当てますが、IPv6 の考え方は同じであり、最後に一般的な解決策を示します。

    まず、考えられる IP の全範囲があります:0x00000000 から 0xFFFFFFFF に .

    この範囲内には、dhcp_range の範囲 (包括的) によって定義された「島」があります。 :dhcp_range.begin_address, dhcp_range.end_address .割り当てられた IP アドレスのリストは、それぞれ 1 つの要素を持つ島の別のセットと考えることができます:ip_address.address, ip_address.address .最後に、サブネット自体は 2 つの島です:0x00000000, subnet.ipv4_begin および subnet.ipv4_end, 0xFFFFFFFF .

    これらの島々はそうではないことがわかっています オーバーラップすることで、私たちの生活が楽になります。島は互いに完全に隣接することができます。たとえば、連続して割り当てられた IP アドレスがほとんどない場合、それらの間のギャップはゼロです。これらすべてのアイランドの中で、少なくとも 1 つの要素を持つ最初のギャップを見つける必要があります。つまり、ゼロ以外のギャップです。つまり、次のアイランドは前の島が終わってから少し離れたところ。

    UNION を使用してすべての島をまとめます。 (CTE_Islands ) そして end_address の順にそれらすべてを調べます (または begin_address 、インデックスのあるフィールドを使用) および LEAD を使用 先を見て、次の島の開始アドレスを取得します。最後に、各行に end_address が含まれるテーブルができます。 現在の島の begin_address 次の島の (CTE_Diff )。それらの差が複数ある場合は、「ギャップ」が十分に広いことを意味し、end_address を返します。 現在の島 + 1.

    特定のサブネットで使用可能な最初の IP アドレス

    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;
    

    利用可能な IP アドレスが少なくとも 1 つある場合、結果セットには 1 つの行が含まれ、利用可能な IP アドレスがない場合、行はまったく含まれません。

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

    SQLFiddle へのリンクは次のとおりです。 .パラメータで動かなかったので 1 をハードコーディングしました そこの。 UNION を他のサブネット ID (2 または 3) に変更して、他のサブネットを試してください。また、結果を varbinary に表示しませんでした 正しく、bigintのままにしました。たとえば、Windows 電卓を使用して 16 進数に変換し、結果を確認します。

    TOP(1) で結果を最初のギャップに制限しない場合 、利用可能なすべての IP 範囲 (ギャップ) のリストを取得します。

    特定のサブネットで使用可能なすべての IP アドレス範囲のリスト

    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;
    

    結果。 SQL フィドル 16 進数ではなく単純な bigint として結果を取得し、ハードコードされたパラメータ 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
    

    各サブネットで使用可能な最初の IP アドレス

    特定のサブネットを 1 つ指定するのではなく、クエリを拡張してすべてのサブネットの最初に使用可能な IP アドレスを返すのは簡単です。 CROSS APPLY を使用します 各サブネットの島のリストを取得し、PARTITION BY subnet_sk を追加します LEADに 関数。

    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
    

    結果セット

    subnet_sk    NextAvailableIPAddress
    1            0xAC101129
    2            0xC0A81B1F
    3            0xC0A8160C
    

    SQLFiddle はこちら . varbinary への変換を削除する必要がありました

    IPv4 と IPv6 の両方の汎用ソリューション

    すべてのサブネットで使用可能な IP アドレスのすべての範囲

    サンプル IPv4 および IPv6 データ、関数、および最終クエリを使用した SQL Fiddle

    IPv6 のサンプル データが正しくありませんでした - サブネット 0xFC00000000000000FFFFFFFFFFFFFFFF の終わり dhcp 範囲より小さかったので、0xFC0001066800000000000000FFFFFFFF に変更しました .また、同じサブネットに IPv4 と IPv6 の両方があったため、扱いが面倒でした。この例のために、スキーマを少し変更しました - 明示的な ipv4_begin / end の代わりに および ipv6_begin / end subnetip_begin / end だけにしました varbinary(16) として (他のテーブルと同じ)。 address_family も削除しました そうでなければ、SQL Fiddle には大きすぎました。

    算術関数

    IPv6 で機能させるには、1 を加算/減算する方法を理解する必要があります。 to/from binary(16) .そのためにCLR関数を作成します。 CLR を有効にすることが許可されていない場合は、標準の T-SQL を介して可能です。スカラーではなくテーブルを返す関数を 2 つ作成しました。一般的なソリューションを作成したかったので、関数は varbinary(16) を受け入れます IPv4 と IPv6 の両方で機能します。

    varbinary(16) をインクリメントする T-SQL 関数は次のとおりです。 一つ。パラメータの長さが 16 バイトでない場合は、IPv4 であると想定し、単純に bigint に変換します。 1 を追加する そして binary に戻ります .それ以外の場合は、binary(16) を分割します それぞれ長さ 8 バイトの 2 つの部分に分割し、それらを bigint にキャストします。 . bigint は署名されていますが、符号なしのインクリメントが必要なので、いくつかのケースを確認する必要があります。

    else 低い部分が最も一般的です。単純に低い部分を 1 増やし、結果を元の高い部分に追加します。

    下位部分が 0xFFFFFFFFFFFFFFFF の場合 、次に、低部分を 0x0000000000000000 に設定します フラグを持ち越します。つまり、高い部分を 1 つ増やします。

    下位部分が 0x7FFFFFFFFFFFFFFF の場合 、次に、低部分を 0x8000000000000000 に設定します この bigint をインクリメントしようとするため、明示的に 値はオーバーフローを引き起こします。

    整数が 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF の場合 結果を 0x00000000000000000000000000000000 に設定します .

    1 ずつ減らす関数も同様です。

    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
    

    すべてのサブネットで使用可能な IP アドレスのすべての範囲

    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;
    

    結果セット

    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
    

    実行計画

    ここで提案されているさまざまなソリューションがどのように機能するかを知りたいと思ったので、それらの実行計画を調べました。これらの計画は、インデックスのない小さなサンプル データ セット用であることに注意してください。

    IPv4 と IPv6 の両方に対する私の一般的な解決策:

    dnoeth による同様のソリューション :

    cha による解決策 LEAD を使用しない 関数:



    1. MySQLの2つの異なるケースで3つのテーブルをバインドする

    2. 別のテーブルのSELECT(カウント)に基づくmySQLUPDATEテーブル

    3. SQLServerSELECTを既存のテーブルに追加

    4. MySQLは、ビット単位の関数を使用してBITフィールドをクエリするときにインデックスを使用しません