都内で働くSEの技術的なひとりごと / Technical soliloquy of System Engineer working in Tokyo

都内でサラリーマンやってます。SQL Server を中心とした (2023年からは Azure も。) マイクロソフト系(たまに、OSS系などマイクロソフト以外の技術も...)の技術的なことについて書いています。日々の仕事の中で、気になったことを技術要素関係なく気まぐれに選んでいるので記事内容は開発言語、インフラ等ばらばらです。なお、当ブログで発信、発言は私個人のものであり、所属する組織、企業、団体等とは何のかかわりもございません。ブログの内容もきちんと検証して使用してください。英語の勉強のため、英語の

大量の IN 句の動作を見てみた

 お久しぶりです。最近は、激しい移動すなわち、「 #ダイヤモンド修行 」 が激しさを増しており、記事さえ中々書けない事態に陥っています。今回はそんなのやるなら初めからちゃんと JOIN したりするのを前提にクエリ設計したらいいやん!と批判を浴びそうな気がしますが、ちょっと気になったので簡単に検証してみます。

下記のような恐ろしい クエリ1 と クエリ2 を用意します。
※クエリ1

SELECT
    [SalesOrderID],
    [RevisionNumber],
    [OrderDate],
    [DueDate],
    [ShipDate],
    [Status],
    [OnlineOrderFlag],
    [SalesOrderNumber],
    [PurchaseOrderNumber],
    [AccountNumber],
    [CustomerID],
    [SalesPersonID],
    [TerritoryID],
    [BillToAddressID],
    [ShipToAddressID],
    [ShipMethodID],
    [CreditCardID],
    [CreditCardApprovalCode],
    [CurrencyRateID],
    [SubTotal],
    [TaxAmt],
    [Freight],
    [TotalDue],
    [Comment],
    [rowguid],
    [ModifiedDate]
FROM
    [AdventureWorks].[Sales].[SalesOrderHeader]
WHERE
    SalesOrderID IN('43659', '43660', '43661', '43662', '43663', '43664', '43665', '43666', '43667', '43668', '43669', '43670', '43671', '43672', '43673', '43674', '43675', '43676', '43677', '43678', '43679', '43680', '43681', '43682', '43683', '43684', '43685', '43686', '43687', '43688', '43689', '43690', '43691', '43692', '43693', '43694', '43695', '43696', '43697', '43698', '43699', '43700', '43701', '43702', '43703', '43704', '43705', '43706', '43707', '43708', '43709', '43710', '43711', '43712', '43713', '43714', '43715', '43716', '43717', '43718', '43719', '43720', '43721', '43722', '43723', '43724', '43725', '43726', '43727', '43728', '43729', '43730', '43731', '43732', '43733', '43734', '43735', '43736', '43737', '43738', '43739', '43740', '43741', '43742', '43743', '43744', '43745', '43746', '43747', '43748', '43749', '43750', '43751', '43752', '43753', '43754', '43755', '43756', '43757', '43758', '43759', '43760', '43761', '43762', '43763', '43764', '43765', '43766', '43767', '43768', '43769', '43770', '43771', '43772', '43773', '43774', '43775', '43776', '43777', '43778', '43779', '43780', '43781', '43782', '43783', '43784', '43785', '43786', '43787', '43788', '43789', '43790', '43791', '43792', '43793', '43794', '43795', '43796', '43797', '43798', '43799', '43800', '43801', '43802', '43803', '43804', '43805', '43806', '43807', '43808', '43809', '43810', '43811', '43812', '43813', '43814', '43815', '43816', '43817', '43818', '43819', '43820', '43821', '43822', '43823', '43824', '43825', '43826', '43827', '43828', '43829', '43830', '43831', '43832', '43833', '43834', '43835', '43836', '43837', '43838', '43839', '43840', '43841', '43842', '43843', '43844', '43845', '43846', '43847', '43848', '43849', '43850', '43851', '43852', '43853', '43854', '43855', '43856', '43857', '43858', '43859', '43860', '43861', '43862', '43863', '43864', '43865', '43866', '43867', '43868', '43869', '43870', '43871', '43872', '43873', '43874', '43875', '43876', '43877', '43878', '43879', '43880', '43881', '43882', '43883', '43884', '43885', '43886', '43887', '43888', '43889', '43890', '43891', '43892', '43893', '43894', '43895', '43896', '43897', '43898', '43899', '43900', '43901', '43902', '43903', '43904', '43905', '43906', '43907', '43908')

※クエリ2

SELECT
    [SalesOrderID],
    [RevisionNumber],
    [OrderDate],
    [DueDate],
    [ShipDate],
    [Status],
    [OnlineOrderFlag],
    [SalesOrderNumber],
    [PurchaseOrderNumber],
    [AccountNumber],
    [CustomerID],
    [SalesPersonID],
    [TerritoryID],
    [BillToAddressID],
    [ShipToAddressID],
    [ShipMethodID],
    [CreditCardID],
    [CreditCardApprovalCode],
    [CurrencyRateID],
    [SubTotal],
    [TaxAmt],
    [Freight],
    [TotalDue],
    [Comment],
    [rowguid],
    [ModifiedDate]
FROM
    [AdventureWorks].[Sales].[SalesOrderHeader]
WHERE
    SalesOrderID IN('43873', '43660', '43824', '43739', '43694', '43898', '43902', '43713', '43700', '43664', '43661', '43745', '43708', '43901', '43741', '43794', '43848', '43755', '43888', '43687', '43813', '43670', '43781', '43840', '43817', '43857', '43853', '43759', '43815', '43810', '43711', '43750', '43830', '43723', '43698', '43754', '43879', '43748', '43778', '43777', '43753', '43870', '43742', '43728', '43691', '43668', '43702', '43707', '43818', '43811', '43780', '43847', '43763', '43784', '43837', '43680', '43860', '43862', '43866', '43821', '43906', '43838', '43699', '43861', '43894', '43666', '43751', '43785', '43872', '43725', '43877', '43744', '43782', '43798', '43743', '43752', '43662', '43793', '43676', '43683', '43747', '43677', '43796', '43667', '43669', '43867', '43722', '43709', '43718', '43692', '43829', '43730', '43881', '43726', '43773', '43834', '43825', '43695', '43688', '43889', '43712', '43800', '43826', '43775', '43899', '43799', '43885', '43789', '43684', '43802', '43740', '43845', '43863', '43703', '43724', '43736', '43704', '43721', '43792', '43882', '43904', '43682', '43851', '43842', '43895', '43774', '43868', '43900', '43729', '43779', '43672', '43887', '43675', '43673', '43832', '43880', '43697', '43831', '43874', '43749', '43883', '43823', '43905', '43715', '43737', '43701', '43858', '43731', '43766', '43689', '43714', '43852', '43735', '43764', '43772', '43686', '43890', '43756', '43762', '43706', '43787', '43822', '43807', '43671', '43770', '43681', '43804', '43679', '43801', '43841', '43720', '43833', '43908', '43827', '43812', '43797', '43663', '43816', '43719', '43850', '43814', '43907', '43876', '43896', '43871', '43819', '43835', '43839', '43884', '43659', '43761', '43765', '43768', '43705', '43696', '43674', '43869', '43854', '43859', '43886', '43678', '43716', '43769', '43795', '43892', '43808', '43878', '43685', '43844', '43710', '43803', '43903', '43786', '43836', '43809', '43849', '43843', '43746', '43897', '43864', '43790', '43875', '43856', '43805', '43791', '43734', '43733', '43776', '43846', '43727', '43820', '43788', '43690', '43865', '43738', '43665', '43771', '43767', '43828', '43758', '43783', '43757', '43893', '43855', '43693', '43806', '43891', '43732', '43717', '43760')

 クエリ1 の IN 句の条件は、SalesOrderID が昇順で並び替えられています。クエリ2 の IN 句の条件はランダムに並び替えられています。それぞれ計測してみます。

  • 1回目f:id:koogucc11:20180305001430p:plain
  • 2回目f:id:koogucc11:20180305001435p:plain
  • 3回目f:id:koogucc11:20180305001440p:plain
  • 4回目f:id:koogucc11:20180305001445p:plain
  • 5回目f:id:koogucc11:20180305001449p:plain

インデックスの並び順で IN 句指定されたほうが明らかに高速ですね。もう少し詳しくみてみましょう。SET STATISTICS PROFILE ON を付加します。
f:id:koogucc11:20180305002230p:plain

指定した IN 句の条件がきちんと並び替えられていますね。レスポンス差は内部的にこのソートの負荷が生じた結果なのでしょうか。

VALUES:(((43659)),((43660)),((43661)),((43662)),((43663)),((43664)),((43665)),((43666)),((43667)),((43668)),((43669)),((43670)),((43671)),((43672)),((43673)),((43674)),((43675)),((43676)),((43677)),((43678)),((43679)),((43680)),((43681)),((43682)),((43683)),((43684)),((43685)),((43686)),((43687)),((43688)),((43689)),((43690)),((43691)),((43692)),((43693)),((43694)),((43695)),((43696)),((43697)),((43698)),((43699)),((43700)),((43701)),((43702)),((43703)),((43704)),((43705)),((43706)),((43707)),((43708)),((43709)),((43710)),((43711)),((43712)),((43713)),((43714)),((43715)),((43716)),((43717)),((43718)),((43719)),((43720)),((43721)),((43722)),((43723)),((43724)),((43725)),((43726)),((43727)),((43728)),((43729)),((43730)),((43731)),((43732)),((43733)),((43734)),((43735)),((43736)),((43737)),((43738)),((43739)),((43740)),((43741)),((43742)),((43743)),((43744)),((43745)),((43746)),((43747)),((43748)),((43749)),((43750)),((43751)),((43752)),((43753)),((43754)),((43755)),((43756)),((43757)),((43758)),((43759)),((43760)),((43761)),((43762)),((43763)),((43764)),((43765)),((43766)),((43767)),((43768)),((43769)),((43770)),((43771)),((43772)),((43773)),((43774)),((43775)),((43776)),((43777)),((43778)),((43779)),((43780)),((43781)),((43782)),((43783)),((43784)),((43785)),((43786)),((43787)),((43788)),((43789)),((43790)),((43791)),((43792)),((43793)),((43794)),((43795)),((43796)),((43797)),((43798)),((43799)),((43800)),((43801)),((43802)),((43803)),((43804)),((43805)),((43806)),((43807)),((43808)),((43809)),((43810)),((43811)),((43812)),((43813)),((43814)),((43815)),((43816)),((43817)),((43818)),((43819)),((43820)),((43821)),((43822)),((43823)),((43824)),((43825)),((43826)),((43827)),((43828)),((43829)),((43830)),((43831)),((43832)),((43833)),((43834)),((43835)),((43836)),((43837)),((43838)),((43839)),((43840)),((43841)),((43842)),((43843)),((43844)),((43845)),((43846)),((43847)),((43848)),((43849)),((43850)),((43851)),((43852)),((43853)),((43854)),((43855)),((43856)),((43857)),((43858)),((43859)),((43860)),((43861)),((43862)),((43863)),((43864)),((43865)),((43866)),((43867)),((43868)),((43869)),((43870)),((43871)),((43872)),((43873)),((43874)),((43875)),((43876)),((43877)),((43878)),((43879)),((43880)),((43881)),((43882)),((43883)),((43884)),((43885)),((43886)),((43887)),((43888)),((43889)),((43890)),((43891)),((43892)),((43893)),((43894)),((43895)),((43896)),((43897)),((43898)),((43899)),((43900)),((43901)),((43902)),((43903)),((43904)),((43905)),((43906)),((43907)),((43908)))

時間あったら、読んでみたい。

SQL Server 2017 Administrator's Guide

SQL Server 2017 Administrator's Guide

新年の挨拶してみる

 新年あけましておめでとうございます。旧年は大変お世話になりました。今年も【都内で働くSEの技術的なひとりごと】のご購読よろしくお願いいたします。今年もダイヤモンド修行頑張りますww
f:id:koogucc11:20180101141908p:plain

今年書いた記事を振り返ってみる

 今年は去年と比較すると、投稿数は激減しました。
f:id:koogucc11:20171231230142p:plain

 去年は、120。今年は36。
f:id:koogucc11:20171231230337p:plain

 今年は特に新しいことにも取り組まず、仕事に忙殺されていた感じです。そういう今も仕事の合間に書いてます。来年はもう少しテクニカルなことに取り組まないとなぁと思ってます。あと、オープンソース

 来年もどうぞよろしくお願いいたします。それでは、よいお年をお迎えください♪
 

反省

反省

デッドロックについて説明してみる - その 7 ( C# から sp_getapplock と sp_releaseapplock 使ってデッドロックを回避してみる ) -

 過去に C# からデッドロックを例外処理でキャッチするサンプルを作成しました。
ryuchan.hatenablog.com

 このサンプルソースに、p_getapplock と sp_releaseapplock を適用し、デッドロックを回避します。まず、それぞれのストアドをコールするロジックを作りましょう。

public int GetAppLock(SqlConnection connection, SqlTransaction transaction)
{
    var command = new SqlCommand("sp_getapplock", connection, transaction);
    command.CommandType = CommandType.StoredProcedure;
    command.CommandTimeout = 60000;

    command.Parameters.AddWithValue("Resource", "lock_resource_id");
    command.Parameters.AddWithValue("LockMode", "Exclusive");
    command.Parameters.AddWithValue("LockOwner", "Transaction");
    command.Parameters.AddWithValue("LockTimeout", 60000);

    var retCode = command.Parameters.Add("ReturnValue", SqlDbType.Int);
    retCode.Direction = ParameterDirection.ReturnValue;
    command.ExecuteNonQuery();

    return (int)retCode.Value;
}

public int ReleaseAppLock(SqlConnection connection, SqlTransaction transaction)
{
    var command = new SqlCommand("sp_releaseapplock", connection, transaction);
    command.CommandType = CommandType.StoredProcedure;
    command.CommandTimeout = 60000;

    command.Parameters.AddWithValue("Resource", "lock_resource_id");

    var retCode = command.Parameters.Add("ReturnValue", SqlDbType.Int);
    retCode.Direction = ParameterDirection.ReturnValue;
    command.ExecuteNonQuery();

    return (int)retCode.Value;
}

 下記のように組み込みます。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    var command = connection.CreateCommand();

    while (0 == 0)
    {
        try
        {
            var transaction = connection.BeginTransaction("query1");
            if (GetAppLock(connection, transaction) >= 0)
            {

                command.Connection = connection;
                command.Transaction = transaction;

                command.CommandText = @"UPDATE 
                            Production.Product
                        SET Name = N'Bearing Ball'
                        WHERE
                            ProductID = 2;";
                command.ExecuteNonQuery();

                command.CommandText = @"UPDATE 
                            Production.Product
                        SET Name = N'Adjustable Race'
                        WHERE
                            ProductID = 1;";
                command.ExecuteNonQuery();
            }
            ReleaseAppLock(connection, transaction);
            transaction.Commit();
            transaction.Dispose();
        }
        catch (SqlException ex)
        {
            MessageBox.Show("エラーNo:" + ex.Number.ToString() + " エラーメッセージ:" + ex.Message.ToString());
            break;
        }
    }
    command.Dispose();
}

 組み込みが完了したら、実行してみましょう。デッドロックが発生しません。dm_tran_locks でアプリケーションロックが発生しているのが判断できます。
f:id:koogucc11:20171230125822p:plain

 LockOwner が Transaction の場合は、明示的に sp_releaseapplock はコールしなくてもいいようですが、個人的に気持ち悪いのでサンプルソースでは呼んでます。さて、今年も残すところあと2日。けど、私は明日から一月中旬までずっとお仕事です....いつになったら、ゆっくり正月を迎えられるようになるんでしょうか....

好奇心を“天職

好奇心を“天職"に変える空想教室

天職は寝て待て?新しい転職・就活・キャリア論? (光文社新書)

天職は寝て待て?新しい転職・就活・キャリア論? (光文社新書)

デッドロックについて説明してみる - その 6 ( sp_getapplock と sp_releaseapplock でデッドロックを回避してみる ) -

 今年のダイヤモンド修行は例年になく辛いものでした。今年は関西方面の出張が多かったこともあり FLY ON ポイントが中々貯まらず、挫けそうな時もありましたが(笑)、なんとか達成できました。
f:id:koogucc11:20171224093914p:plain

 インデックスが上手く使われていないことも辛いことですが、デッドロックが発生してしまいデータが...とかなると更にツラいですよね。今回は、sp_getapplock と sp_releaseapplock を使用してデッドロックを回避する方法について説明します。過去にデッドロックについていくつか記事を書いています。今回の記事と合わせて参照してみてください。
ryuchan.hatenablog.com
ryuchan.hatenablog.com
ryuchan.hatenablog.com
ryuchan.hatenablog.com
ryuchan.hatenablog.com

 下記のような二つのクエリを同時に実行すると、残念ながらデッドロックが発生してしまいます。(昔の記事で使用したクエリを使用します。よく見るとひどいクエリだ...)

  • クエリ 1
    DECLARE @Error_Message  NVARCHAR(MAX);
    DECLARE @Error_Severity INT;
    DECLARE @Error_State    INT;
    
    WHILE 0 = 0
    BEGIN
        BEGIN TRY
            BEGIN TRAN
            -- ProductID = 2 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Bearing Ball' 
            WHERE 
                ProductID = 2 
    
            -- ProductID = 1 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Adjustable Race'
            WHERE 
                ProductID = 1 
            COMMIT TRAN
        END TRY
    
        -- 例外をキャッチしたら、メッセージを出力して処理を終了する。
        BEGIN CATCH
            SET @Error_Message  = ERROR_MESSAGE();
            SET @Error_Severity = ERROR_SEVERITY();
            SET @Error_State    = ERROR_STATE();
            RAISERROR(@Error_Message, @Error_Severity, @Error_State);
            BREAK;
        END CATCH
    END
    

  • クエリ 2
    DECLARE @Error_Message  NVARCHAR(MAX);
    DECLARE @Error_Severity INT;
    DECLARE @Error_State    INT;
    
    WHILE 0 = 0
    BEGIN
        BEGIN TRY
            BEGIN TRAN 
            -- ProductID = 1 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Adjustable Race'
            WHERE 
                ProductID = 1 
    
            -- ProductID = 2 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Bearing Ball' 
            WHERE 
                ProductID = 2 
            COMMIT TRAN
        END TRY
    
        -- 例外をキャッチしたら、メッセージを出力して処理を終了する。	
        BEGIN CATCH
            SET @Error_Message  = ERROR_MESSAGE();
            SET @Error_Severity = ERROR_SEVERITY();
            SET @Error_State    = ERROR_STATE();
            RAISERROR(@Error_Message, @Error_Severity, @Error_State);
            BREAK;
        END CATCH
    END
    

 上記のクエリに、sp_getapplocksp_releaseapplock の処理を加えてみます。

  • クエリ 1
    DECLARE @Error_Message  NVARCHAR(MAX);
    DECLARE @Error_Severity INT;
    DECLARE @Error_State    INT;
    DECLARE @Return_Code    INT;
    
    WHILE 0 = 0
    BEGIN
        BEGIN TRY
            BEGIN TRAN
            -- アプリケーションロックの獲得
            EXEC @Return_Code = sp_getapplock @Resource = 'lock_resource_id',
                                              @LockMode = 'Exclusive',
                                              @LockOwner = 'Transaction',
                                              @LockTimeout = 60000
            IF @Return_Code >= 0
            BEGIN
    		-- ProductID = 2 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Bearing Ball' 
            WHERE 
                ProductID = 2 
    
            -- ProductID = 1 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Adjustable Race'
            WHERE 
                ProductID = 1 
            -- アプリケーションロックの解放
            EXEC sp_releaseapplock @Resource = 'lock_resource_id'
            END 
            COMMIT TRAN
        END TRY
    
        -- 例外をキャッチしたら、メッセージを出力して処理を終了する。
        BEGIN CATCH
            -- アプリケーションロックの解放
            EXEC sp_releaseapplock @Resource = 'lock_resource_id'
            ROLLBACK
            SET @Error_Message  = ERROR_MESSAGE();
            SET @Error_Severity = ERROR_SEVERITY();
            SET @Error_State    = ERROR_STATE();
            RAISERROR(@Error_Message, @Error_Severity, @Error_State);
            BREAK;
        END CATCH
    END
    

  • クエリ 2
    DECLARE @Error_Message  NVARCHAR(MAX);
    DECLARE @Error_Severity INT;
    DECLARE @Error_State    INT;
    DECLARE @Return_Code    INT;
    
    WHILE 0 = 0
    BEGIN
        BEGIN TRY
            BEGIN TRAN 
            -- アプリケーションロックの獲得
            EXEC @Return_Code = sp_getapplock @Resource = 'lock_resource_id',
                                              @LockMode = 'Exclusive',
                                              @LockOwner = 'Transaction',
                                              @LockTimeout = 60000
            IF @Return_Code >= 0
            BEGIN
            -- ProductID = 1 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Adjustable Race'
            WHERE 
                ProductID = 1 
    
            -- ProductID = 2 のデータを更新する。
            UPDATE 
                Production.Product
            SET Name = N'Bearing Ball' 
            WHERE 
                ProductID = 2 
            -- アプリケーションロックの解放 
            EXEC sp_releaseapplock @Resource = 'lock_resource_id'
            END
            COMMIT TRAN
        END TRY
    
        -- 例外をキャッチしたら、メッセージを出力して処理を終了する。	
        BEGIN CATCH
            -- アプリケーションロックの解放
            EXEC sp_releaseapplock @Resource = 'lock_resource_id'
            ROLLBACK
            SET @Error_Message  = ERROR_MESSAGE();
            SET @Error_Severity = ERROR_SEVERITY();
            SET @Error_State    = ERROR_STATE();
            RAISERROR(@Error_Message, @Error_Severity, @Error_State);
            BREAK;
        END CATCH
    END
    

 SQL Server Management Studio で上記のクエリを実行してみましょう。デッドロックは発生しません。
f:id:koogucc11:20171223112651p:plain

 sys.dm_tran_locks から APPLICATION ロックが獲得されているのを確認できます。
f:id:koogucc11:20171223113013p:plain

 通常デッドロックを回避するにはリソース更新順番などを見直すのがベストです。アプリケーションロックを使用するとそれなりに負荷も上がるので、適用するには注意が必要です。
ryuchan.hatenablog.com

 新年会は、お肉!赤身最高。
【東京都内】肉通も太鼓判!絶品肉料理に出会えるお店17選 [食べログまとめ]

「よろにく」さん良さそう....

よろにく

食べログ よろにく

 今年の子供のクリスマスプレゼントは Switch 。なんとか手に入った。

 それ以外にも色々と購入。

【Nintendo Switch対応】全面保護PCカバー for Nintendo Switch

【Nintendo Switch対応】全面保護PCカバー for Nintendo Switch

 Switch をやっていると、結構バッテリーを消耗するようなので、自分が今使っているモバイルバッテリーを子供にあげて、自分用に MacBook Pro も充電できるモバイルバッテリーを購入。ParallelsWindows を使っていると異常にバッテリーを使うので必須ですね。丸一日のカンファレンスに参加しても安心です。

Docker for Mac を試してみる

 全国飛び回りの毎日です。前回、macOS 版の sqlops を試したので、その他にも macOS で何か試したくなりました。
ryuchan.hatenablog.com

 今更感はありますが、Docker を試したいと思います。Docker for Mac をダウンロードし、インストールします。
f:id:koogucc11:20171119172632p:plain

 3.25 GB 以上が推奨のようです。ここではとりあえず 3.5 GB にしておきます。
f:id:koogucc11:20171120094614p:plain

 次に Docker に対応した SQL Server をインストールします。下記のコマンドをターミナルから実行します。( 詳しくはココ。 )

$ docker pull microsoft/mssql-server-linux

f:id:koogucc11:20171119221524p:plain

 イメージインストールに成功したら、下記のコマンドを実行し、Docker イメージの SQL Server を実行します。( 詳しくはココ。 )

$ docker run -e ACCEPT_EULA=Y -e SA_PASSWORD=10n@ideh@t@r@ku -p 1433:1433 -d microsoft/mssql-server-linux

f:id:koogucc11:20171119221732p:plain

 SQL Operations Studio から Docker 上の SQL Server に接続してみましょう。sa で接続しましょう。IP は Docker が動作しているクライアントの IP アドレスを設定します。
f:id:koogucc11:20171119222338p:plain

 下記のクエリを実行してみましょう。

SELECT @@VERSION

f:id:koogucc11:20171119222630p:plain

Microsoft SQL Server 2017 (RTM-CU1) (KB4038634) - 14.0.3006.16 (X64) 	Oct 19 2017 02:42:29 	Copyright (C) 2017 Microsoft Corporation	Developer Edition (64-bit) on Linux (Ubuntu 16.04.3 LTS)

 手軽に SQL Server の環境を準備できますね。複数の環境を素早く作成しなければいけない時に重宝しそうですね。Docker 楽しいなぁ。

Docker

Docker

Docker入門

Docker入門