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

SQLケース:3つのあまり知られていない面倒を知って回避する

    SQLケース?ケーキ!

    本当に?

    ランタイムエラーやパフォーマンスの低下を引き起こす可能性のある3つの厄介な問題に遭遇するまでは。

    小見出しをスキャンして問題が何であるかを確認しようとしている場合、私はあなたを責めることはできません。私を含む読者は焦ります。

    SQL CASEの基本をすでにご存知だと思いますので、長い紹介で退屈することはありません。内部で何が起こっているのかをより深く理解しましょう。

    1。 SQLCASEは常に順次評価するとは限りません

    Microsoft SQL CASEステートメントの式は、ほとんどの場合、順番に、または左から右に評価されます。ただし、集計関数で使用する場合は別の話になります。例を見てみましょう:

    -- aggregate function evaluated first and generated an error
    DECLARE @value INT = 0;
    SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;
    

    上記のコードは正常に見えます。これらのステートメントの結果を尋ねると、おそらく1と言えます。目視検査では、@ valueが0に設定されているため、結果は1であることがわかります。

    しかし、ここではそうではありません。 SQL Server Management Studioの実際の結果をご覧ください:

    Msg 8134, Level 16, State 1, Line 4
    Divide by zero error encountered.
    

    しかし、なぜですか?

    条件式がSQLCASEのMAX()のような集計関数を使用する場合、最初に評価されます。したがって、@ valueがゼロであるため、MAX(1 / @ value)はゼロ除算エラーを引き起こします。

    この状況は、隠されているとさらに厄介です。後で説明します。

    2。単純なSQLCASE式は複数回評価します

    だから何?

    良い質問。実は、リテラルや単純な式を使えば、何の問題もありません。ただし、サブクエリを条件式として使用すると、大きな驚きがあります。

    以下の例を試す前に、ここからデータベースのコピーを復元することをお勧めします。残りの例ではこれを使用します。

    ここで、この非常に単純なクエリについて考えてみましょう。

    
    SELECT TOP 1 manufacturerID FROM SportsCars
    
    

    とても簡単ですよね? 1行のデータと1列のデータを返します。 STATISTICS IOは、最小限の論理読み取りを明らかにします。

    クイックノート :初心者の場合、論理読み取りが多いとクエリが遅くなります。詳細についてはこちらをお読みください。

    実行計画では、簡単なプロセスも明らかになっています。

    それでは、そのクエリをサブクエリとしてCASE式に入れましょう:

    -- Using a subquery in a SQL CASE
    DECLARE @manufacturer NVARCHAR(50)
    
    SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
    				WHEN 6 THEN 'Alfa Romeo'
    				WHEN 21 THEN 'Aston Martin'
    				WHEN 64 THEN 'Ferrari'
    				WHEN 108 THEN 'McLaren'
    				ELSE 'Others'
    		     END)
    
    SELECT @manufacturer;
    

    分析

    論理的な読み取りが4回吹き飛ばされるため、指を交差させてください。

    サプライズ!論理読み取りが2つしかない図1と比較すると、これは4倍高くなっています。したがって、クエリは4倍遅くなります。どうしてそれが起こるのでしょうか?サブクエリは1回しか表示されませんでした。

    しかし、それで話は終わりではありません。実行計画を確認してください:

    図4にトップスキャンとインデックススキャンの演算子の4つのインスタンスが表示されます。各トップスキャンとインデックススキャンが2つの論理読み取りを消費する場合、図3で論理読み取りが8になった理由を説明します。また、各トップスキャンとインデックススキャンのコストは25%です。 、それはまた、それらが同じであることを示しています。

    しかし、それだけではありません。 Compute Scalar演算子のプロパティは、ステートメント全体がどのように扱われるかを示します。

    ComputeScalar演算子DefinedValuesからの4つのCASEWHEN式が表示されます。単純なCASE式が次のような検索されたCASE式になったようです:

    DECLARE @manufacturer NVARCHAR(50)
    
    SET @manufacturer = (CASE 
    		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
    		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
    		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
    		     WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
    		     ELSE 'Others'
    		     END)
    
    SELECT @manufacturer;
    

    要約しましょう。トップおよびインデックススキャン演算子ごとに2つの論理読み取りがありました。これに4を掛けると、8つの論理読み取りが行われます。 ComputeScalar演算子で4つのCASEWHEN式も確認しました。

    最終的に、単純なCASE式のサブクエリが4回評価されました。 これにより、クエリが遅れます。

    単純なCASE式でサブクエリの複数の評価を回避する方法

    SQLの複数のCASEステートメントなどのパフォーマンスの問題を回避するには、クエリを書き直す必要があります。

    まず、サブクエリの結果を変数に入れます。次に、次のように、単純なSQLServerCASE式の条件でその変数を使用します。

    DECLARE @manufacturer NVARCHAR(50)
    DECLARE @ManufacturerID INT -- create a new variable
    
    -- store the result of the subquery in a variable
    SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars) 
    
    -- use the new variable in the simple CASE expression
    SET @manufacturer = (CASE @ManufacturerID
    		     WHEN 6 THEN 'Alfa Romeo'
    		     WHEN 21 THEN 'Aston Martin'
    		     WHEN 64 THEN 'Ferrari'
    		     WHEN 108 THEN 'McLaren'
    		     ELSE 'Others'
    		     END)
    		
    SELECT @manufacturer;
    

    これは良い修正ですか? STATISTICSIOの論理読み取りを見てみましょう:

    変更されたクエリからの論理読み取りが少なくなっています。サブクエリを取り出して結果を変数に割り当てる方がはるかに優れています。実行計画はどうですか?以下をご覧ください。

    トップスキャンとインデックススキャンの演算子は、4回ではなく、1回だけ表示されました。素晴らしい!

    持ち帰り :CASE式の条件としてサブクエリを使用しないでください。値を取得する必要がある場合は、サブクエリの結果を最初に変数に入れます。次に、その変数をCASE式で使用します。

    3。これらの3つの組み込み関数は密かにSQLCASEに変換されます

    秘密があり、SQLServerCASEステートメントはそれと関係があります。これらの3つの機能がどのように動作するかわからない場合は、前のポイント1と2で回避しようとした間違いを犯していることがわかりません。ここにあります:

    • IIF
    • COALESCE
    • 選択

    それらを1つずつ調べてみましょう。

    IIF

    VisualBasicおよびVisualBasicforApplicationsでImmediateIF(IIF)を使用しました。これは、C#の3項演算子と同等です。<条件>?

    条件が与えられたこの関数は、条件の結果に基づいて2つの引数のうちの1つを返します。また、この関数はT-SQLでも使用できます。 WHERE句のCASEステートメントはSELECTステートメント内で使用できます

    しかし、それはより長いCASE表現の単なるシュガーコートです。どうやって知るの?例を見てみましょう。

    SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');

    このクエリの結果は「いいえ」です。ただし、ComputeScalarのプロパティとともに実行プランを確認してください。

    IIFはケースであるため、このようなことを実行するとどうなると思いますか?

    DECLARE @averageCost MONEY = 1000000.00;
    DECLARE @noOfPayments TINYINT = 0;   -- intentional to force the error
    
    SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));
    

    これにより、@ noOfPaymentsが0の場合、ゼロ除算エラーが発生します。同じことが、以前のポイント#1でも発生しました。

    上記のクエリはTRUEになり、83333.33を返す必要があるため、このエラーの原因について疑問に思われるかもしれません。ポイント#1をもう一度確認してください。

    したがって、IIFを使用しているときにこのようなエラーが発生する場合は、SQLCASEが原因です。

    COALESCE

    COALESCEは、SQLCASE式のショートカットでもあります。値のリストを評価し、最初のnull以外の値を返します。 COALESCEに関する前回の記事では、サブクエリを2回評価する例を示しました。しかし、別の方法を使用して、実行プランのSQLCASEを明らかにしました。同じ手法を使用する別の例を次に示します。

    SELECT 
    COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car 
    FROM SportsCars sc
    LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID
    

    実行プランとスカラー定義値の計算を見てみましょう。

    SQLCASEは大丈夫です。 COALESCEキーワードは、[定義された値]ウィンドウのどこにもありません。これは、この機能の背後にある秘密を証明しています。

    しかし、それだけではありません。 [Vehicles]。[dbo]。[Styles]。[Style]を何回見ましたか [定義された値]ウィンドウで? TWICE!これは、Microsoftの公式ドキュメントと一致しています。 COALESCEの引数の1つがサブクエリである場合を想像してみてください。次に、論理読み取りを2倍にして、実行速度も遅くします。

    選択

    最後に、選択します。これは、MSAccessCHOOSE機能に似ています。インデックス位置に基づいて値のリストから1つの値を返します。配列へのインデックスとしても機能します。

    例を使用して、SQLCASEへの変換を掘り下げることができるかどうかを見てみましょう。以下のコードを確認してください:

    ;WITH McLarenCars AS 
    (
    SELECT 
     CASE 
    	WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
    	ELSE '2'
     END AS [type]
    ,sc.Model
    ,s.Style
    FROM SportsCars sc
    INNER JOIN Styles s ON sc.StyleID = s.StyleID
    WHERE sc.ManufacturerID = 108
    )
    SELECT 
     Model
    ,Style
    ,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
    FROM McLarenCars
    

    CHOOSEの例があります。それでは、実行プランとスカラー定義値の計算を確認しましょう:

    図10の[定義済みの値]ウィンドウにキーワードCHOOSEが表示されていますか? CASE WHENはどうですか?

    前の例のように、このCHOOSE関数は、より長いCASE式への単なるシュガーコートです。また、クエリにはCHOOSEの項目が2つあるため、CASEWHENキーワードが2回表示されました。赤いボックスで囲まれた[定義済みの値]ウィンドウを参照してください。

    ただし、ここではSQLに複数のCASEWHENがあります。これは、CTEの内部クエリのCASE式が原因です。注意深く見ると、内部クエリのその部分も2回表示されます。

    要点

    秘密が明かされた今、私たちは何を学びましたか?

    1. SQL CASEは、集計関数を使用すると動作が異なります。 MIN、MAX、COUNTなどの集計関数に引数を渡すときは注意してください。
    2. 単純なCASE式は複数回評価されます。これに注意して、サブクエリを渡さないようにしてください。構文的には正しいですが、パフォーマンスは低下します。
    3. IIF、CHOOSE、およびCOALESCEには汚い秘密があります。これらの関数に値を渡す前に、この点に注意してください。 SQLCASEに変換されます。値に応じて、エラーまたはパフォーマンスの低下が発生します。

    SQLCASEに対するこの異なる見方がお役に立てば幸いです。もしそうなら、あなたの開発者の友人もそれを好きかもしれません。お気に入りのソーシャルメディアプラットフォームで共有してください。そして、コメントセクションであなたがそれについてどう思うか教えてください。


    1. SQLServerデータベースでIDの増分が急増しています

    2. T-sql-値が整数かどうかを判断します

    3. SSHトンネルを介してMySQLサーバーにリモートアクセス

    4. Oraclejdbcクライアントのデフォルトのnls_date_formatを変更する方法