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

1つのスポットでSQLCTEについて知っておくべきことすべて

    KarlがSQLServerCTEについて初めて聞いたのは、SQLコードをわかりやすくするための何かを探していたときでした。それを見ると、一種の頭痛の種です。心配している同僚のアントンは、CTEについて彼に尋ねました。カールは、アントンが彼の頭痛について言及していると思った。多分彼はそれをすべて間違って聞いたので、彼は「もちろん違います」と答えました。面白いことに、彼は慢性外傷性脳症、またCTE(繰り返される頭の怪我によって引き起こされる神経変性疾患)について言及していました。しかし、カールの反応に基づいて、アントンは彼の同僚が彼の言っていることについて無知であることを確かに知っていました。

    CTEを紹介するなんてクレイジーな方法でしょう。では、同じボートに乗る前に、SQLの世界でSQLCTEまたは共通テーブル式とは何であるかを明確にしましょう。

    ここで基本を読むことができます。その間、この珍しい話で何が起こったのかについてもう少し学びます。

    SQLServerのCTEに関する4つの基本事項

    「SQLCTEには名前があります」

    アントンは、SQLCTEが一時的に結果セットと呼ばれるという考えから始めました。一時的な手段であるため、CTEの範囲は限られています。

    「それで、それはサブクエリのようなものですか?」カールは尋ねた。

    「ある意味、そうです。しかし、サブクエリに名前を付けることはできません」とアントン氏は述べています。 「CTEには、名前の付いたテーブルによく似た名前があります。ただし、CREATEの代わりに、WITHを使用して作成します。」次に、彼は紙に構文を書きました:

    WITH <cte_name>(<column list>)
    AS
    (
    <inner query defining the CTE>
    )
    <outer query against CTE>
    

    「SELECTが完了するとCTEは消える」

    アントンは続けてSQLCTEの範囲を説明しました。

    「一時テーブルは、プロシージャのスコープ内またはグローバルに存在できます。しかし、SELECTが完了すると、CTEはなくなります」と彼は韻を踏んで言いました。 「INSERT、UPDATE、またはDELETEに使用する場合も同じです」と彼は続けました。

    「再利用できません」

    「ビューや一時テーブルとは異なり、SQLCTEを再利用することはできません。名前はそこにあるので、内側と外側のクエリ内で参照できます。しかし、それだけです」とアントンは語った。

    「では、SQL CTEの重要な点は何ですか?」カールは尋ねた。

    「コードを読みやすくすることができます」

    「大したこと?」アントンは質問を返しました。 「コードを読みやすくすることができます。それはあなたが探しているものではありませんか?」

    「そうです」とカールは認めた。

    では、カールが行う次の論理的なステップは何ですか?

    SQLのCTEに関する追加情報

    翌日、カールはSQLCTEの探求を続けました。上記とは別に、彼が見つけたものは次のとおりです。

    • SQLCTEは非再帰的または再帰的です。
    • SQL Serverだけでなく、MySQLとOracleもこのアイデアをサポートしています。実際、これはSQL-99仕様の一部です。
    • SQLコードを単純化するために使用されますが、パフォーマンスは向上しません。
    • また、サブクエリや一時テーブルを置き換えることもありません。それぞれに場所と用途があります。

    つまり、クエリを表現するもう1つの方法です

    しかし、カールは詳細に飢えていたので、何がうまくいくか、何がうまくいかないか、そしてサブクエリや一時テーブルと比較してどのように機能するかを探し続けました。

    SQL Server CTEで何が機能しますか?

    CTEについてさらに掘り下げて、KarlはSQLServerが受け入れるものを以下にリストしました。彼の研究も覗いてみてください。

    インラインまたは外部の列エイリアスを割り当てる

    SQL CTEは、列エイリアスの割り当ての2つの形式をサポートしています。 1つ目は、以下の例のようなインライン形式です。

    -- Use an Inline column alias
    
    USE AdventureWorks
    GO;
    
    WITH Sales_CTE
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
    	FROM Sales.SalesOrderHeader  
    	WHERE SalesPersonID IS NOT NULL  
    	GROUP BY SalesPersonID  
    )
    SELECT
     a.SalesPersonID
    ,a.NumberOfOrders
    FROM Sales_CTE a
    

    上記のコードは、SELECTステートメント内で割り当てられるときに、CTE定義内で列エイリアスを使用します。 COUNT(*) AS NumberOfOrdersに気づきましたか ?これがインラインフォームです。

    ここで、別の例は外部フォームです:

    -- Use an external column alias
    
    USE AdventureWorks
    GO;
    
    WITH Sales_CTE(SalesPersonID, NumberOfOrders) 
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*)
    	FROM Sales.SalesOrderHeader  
    	WHERE SalesPersonID IS NOT NULL  
    	GROUP BY SalesPersonID  
    )
    SELECT
     a.SalesPersonID
    ,a.NumberOfOrders
    FROM Sales_CTE a
    

    CTE名を設定した後、列を括弧内に定義することもできます。 WITH Sales_CTE (SalesPersonID、NumberOfOrders)に注意してください 。

    SQLのCTEは、SELECT、INSERT、UPDATE、またはDELETEの前にあります

    この次の項目は、CTEの消費についてです。最初の一般的な例は、SELECTステートメントの前にある場合です。

    -- List down all Salespersons with their all-time number of orders
    USE AdventureWorks
    GO;
    
    WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*)  
    	FROM Sales.SalesOrderHeader  
    	WHERE SalesPersonID IS NOT NULL  
    	GROUP BY SalesPersonID  
    )
    SELECT
     a.SalesPersonID
    ,CONCAT(P.LastName,', ',P.FirstName,' ',P.MiddleName) AS SalesPerson
    ,a.NumberOfOrders
    FROM Sales_CTE a
    INNER JOIN Person.Person p ON a.SalesPersonID = p.BusinessEntityID
    

    この例は何を示していますか?

    • Sales_CTE –CTEの名前。
    • (SalesPersonID、NumberOfOrders) –CTE列の定義。
    • SELECT SalesPersonID、COUNT(*)FROM Sales.SalesOrderHeader WHERE SalesPersonID IS NOT NULL GROUP BY SalesPersonID –CTEを定義する内部SELECT。
    • SELECT a.SalesPersonID、CONCAT(P.LastName、’、‘、P.FirstName、’‘、P.MiddleName)AS SalesPerson –CTEを消費する外部クエリ。この例では、SELECTを使用してCTEを消費します。
    • FROM Sales_CTE a –外部クエリのCTEへの参照。

    SELECTの他に、INSERT、UPDATE、およびDELETEでも機能します。 INSERTの使用例は次のとおりです:

    -- add a 10% increase to Employee 16 after 1 year from the previous increase.
    USE AdventureWorks
    GO;
    
    WITH LatestEmployeePay
    AS
    (
        SELECT TOP 1
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        ,eph.PayFrequency
        FROM HumanResources.EmployeePayHistory eph 
        WHERE eph.BusinessEntityID = 16
        ORDER BY eph.RateChangeDate DESC
    )
    INSERT INTO HumanResources.EmployeePayHistory
    SELECT
     BusinessEntityID
    ,DATEADD(d,365,RateChangeDate)
    ,(Rate * 0.1) + Rate
    ,PayFrequency
    ,GETDATE()
    FROM LatestEmployeePay
    

    上記のリストでは、CTEは従業員16の最新の給与を取得します。CTEの結果セットは、 EmployeePayHistoryに新しいレコードを挿入するために使用されます。 。カールは彼の発見を優雅に記録した。また、彼は適切な例を使用しました。

    1つのクエリで複数のCTEを定義する

    それは正しい。 Karlは、1つのクエリで複数のCTEが可能であることを発見しました。次に例を示します:

    -- Get the present and previous rate of employee 16
    USE AdventureWorks
    GO;
    
    WITH LatestEmployeePay
    AS
    (
        SELECT TOP 1
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        FROM HumanResources.EmployeePayHistory eph 
        WHERE eph.BusinessEntityID = 16
        ORDER BY eph.RateChangeDate DESC
    ),
    PreviousEmployeePay AS
    (
        SELECT TOP 1
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        FROM HumanResources.EmployeePayHistory eph
        INNER JOIN LatestEmployeePay lep 
          ON eph.BusinessEntityID = lep.BusinessEntityID
        WHERE eph.BusinessEntityID = 16
          AND eph.RateChangeDate < lep.RateChangeDate
        ORDER BY eph.RateChangeDate DESC
    )
    SELECT
     a.BusinessEntityID
    ,a.Rate
    ,a.RateChangeDate
    ,b.Rate AS PreviousRate
    FROM LatestEmployeePay a
    INNER JOIN PreviousEmployeePay b 
        ON a.BusinessEntityID = b.BusinessEntityID
    

    上記のコードは、1つのクエリで2つのCTE、つまり LatestEmployeePay を使用します およびPreviousEmployeePay

    CTEを複数回参照

    前の例にはまだまだあります。また、最初のCTEを2番目のCTEに内部結合できることにも注意してください。最後に、外部クエリは2つのCTEの両方に参加できます。 LatestEmployeePay 2回参照されました。

    引数をSQLCTEに渡す

    変数などの引数は、CTEに沿って渡すことができます:

    DECLARE @SalesPersonID INT = 275;
    
    WITH Sales_CTE
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
    	FROM Sales.SalesOrderHeader 
    	WHERE SalesPersonID = @SalesPersonID  
    	GROUP BY SalesPersonID  
    )  
    SELECT SalesPersonID, NumberOfOrders
    FROM Sales_CTE
    

    上記のコードは、変数@ SalesPersonIDを宣言して設定することから始まります。 。次に、値がCTEに渡され、結果がフィルタリングされます。

    カーソルでの使用

    SQLカーソルは、SELECTステートメントを使用して、結果をループできます。また、SQL CTEを使用することもできます:

    DECLARE @SalesPersonID INT
    DECLARE @NumberofOrders INT
    
    DECLARE sales_cursor CURSOR FOR
        WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
    	AS  
    	(  
    		SELECT SalesPersonID, COUNT(*)  
    		FROM Sales.SalesOrderHeader  
    		WHERE SalesPersonID IS NOT NULL  
    		GROUP BY SalesPersonID  
    	)  
    	SELECT salespersonid, numberoforders
    	FROM Sales_CTE; 
    OPEN sales_cursor
    FETCH NEXT FROM sales_cursor INTO @SalesPersonID, @NumberofOrders
    WHILE @@FETCH_STATUS = 0  
    BEGIN
    	PRINT 'SalesPersonID: ' + CAST(@SalesPersonID AS VARCHAR)
    	PRINT '# of Orders: ' + CAST(@NumberofOrders AS VARCHAR)
    	FETCH NEXT FROM sales_cursor  INTO @SalesPersonID, @NumberofOrders
    END
    CLOSE sales_cursor
    DEALLOCATE sales_cursor;
    

    再帰CTEで一時テーブルを使用する

    再帰CTEは、CTE定義内でアンカーメンバーと再帰メンバーを使用します。これは、テーブル内の階層を取得するのに役立ちます。 SQL CTEは、この目的で一時テーブルを使用することもできます。以下の例を参照してください:

    -- Create a Crew table.  
    CREATE TABLE #EnterpriseDSeniorOfficers  
    (  
    CrewID SMALLINT NOT NULL,  
    FirstName NVARCHAR(30)  NOT NULL,  
    LastName  NVARCHAR(40) NOT NULL,  
    CrewRank NVARCHAR(50) NOT NULL,  
    HigherRankID INT NULL,  
     CONSTRAINT PK_CrewID PRIMARY KEY CLUSTERED (CrewID ASC)   
    );  
    -- Populate the table with values.  
    INSERT INTO #EnterpriseDSeniorOfficers VALUES   
     (1, N'Jean-Luc', N'Picard', N'Captain',NULL)  
    ,(2, N'William', N'Riker', N'First Officer',1)  
    ,(3, N'Data', N'', N'Second Officer',1)  
    ,(4, N'Worf', N'', N'Chief of Security',1)  
    ,(5, N'Deanna', N'Troi', N'Ship Counselor',1)  
    ,(6, N'Beveryly', N'Crusher', N'Chief Medical Officer',1)  
    ,(7, N'Geordi', N'LaForge', N'Chief Engineer',1);  
    
    WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
    (  
        SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
        FROM #EnterpriseDSeniorOfficers
        WHERE HigherRankID IS NULL  
        UNION ALL  
        SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
        FROM #EnterpriseDSeniorOfficers AS e  
            INNER JOIN DirectReports AS d  
            ON e.HigherRankID = d.CrewID   
    )  
    SELECT HigherRankID, CrewID, Title, CrewLevel   
    FROM DirectReports  
    OPTION (MAXRECURSION 2)
    ORDER BY HigherRankID;  
    
    DROP TABLE #EnterpriseDSeniorOfficers
    

    カールはこのCTEを解剖して説明しました。方法は次のとおりです。

    アンカーメンバーは、乗組員レベルがゼロ(0)の最初のSELECTステートメントです:

    SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
     FROM #EnterpriseDSeniorOfficers
     WHERE HigherRankID IS NULL
    

    このアンカーメンバーは、階層のルートノードを取得します。 WHERE句は、ルートレベル( HigherRankID IS NULL

    子ノードを取得する再帰メンバーは以下に抽出されます:

    SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
    FROM #EnterpriseDSeniorOfficers AS e  
    INNER JOIN DirectReports AS d  
            ON e.HigherRankID = d.CrewID
    

    OPTION(MAXRECURSION 2)もあります 外部クエリで使用されます。再帰クエリの結果として無限ループが発生すると、再帰CTEが問題になる可能性があります。 MAXRECURSION 2は、この混乱を回避します–ループを2回の再帰のみに制限します。

    これで、カールの機能リストは終了です。しかし、私たちが考えるすべてがうまくいくとは限りません。次のセクションでは、これらに関するKarlの調査結果について説明します。

    SQL CTEで機能しないものは何ですか?

    ここに、SQLCTEを使用するときにエラーを生成するもののリストがあります。

    SQLCTEの前にセミコロンはありません

    CTEの前にステートメントがある場合、そのステートメントはセミコロンで終了する必要があります。 WITH句は、テーブルヒントなどの他の目的でも機能するため、セミコロンを使用するとあいまいさがなくなります。以下のステートメントはエラーを引き起こします:

    DECLARE @SalesPersonID INT
    
    SET @SalesPersonID = 275
    
    WITH Sales_CTE
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
    	FROM Sales.SalesOrderHeader 
    	WHERE SalesPersonID = @SalesPersonID  
    	GROUP BY SalesPersonID  
    )  
    SELECT SalesPersonID, NumberOfOrders
    FROM Sales_CTE
    

    セミコロンでステートメントを終了していなかった初めての人は、このエラーに遭遇します:

    名前のない列

    「列エイリアスを配置するのを忘れましたか?次に、別のエラーが発生します。」カールは彼の論文でこれを述べ、また私が以下に共有するサンプルコードを提供しました:

    DECLARE @SalesPersonID INT
    
    SET @SalesPersonID = 275;
    
    WITH Sales_CTE
    AS  
    (  
    	SELECT SalesPersonID, COUNT(*)
    	FROM Sales.SalesOrderHeader 
    	WHERE SalesPersonID = @SalesPersonID  
    	GROUP BY SalesPersonID  
    )  
    SELECT SalesPersonID, NumberOfOrders
    FROM Sales_CTE
    

    次に、エラーメッセージを見てください:

    列名が重複しています

    上記の#2に関連する別のエラーは、CTE内で同じ列名を使用していることです。通常のSELECTステートメントでそれを回避することはできますが、CTEでは回避できません。カールには別の例がありました:

    WITH Sales_CTE
    AS  
    (  
    	SELECT SalesPersonID AS col1, COUNT(*) AS col1
    	FROM Sales.SalesOrderHeader 
    	GROUP BY SalesPersonID  
    )  
    SELECT *
    FROM Sales_CTE
    

    TOPまたはOFFSET-FETCHのないORDERBY句

    標準SQLでは、結果セットの並べ替えにORDER BYを使用する場合、テーブル式でORDERBYを使用できません。ただし、TOPまたはOFFSET-FETCHが使用されている場合、ORDERBYはフィルタリングの補助になります。

    ORDERBYを使用したKarlの例を次に示します。

    WITH LatestEmployeePay
    AS
    (
        SELECT
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        ,eph.PayFrequency
        FROM HumanResources.EmployeePayHistory eph 
        WHERE eph.BusinessEntityID = 16
        ORDER BY eph.RateChangeDate DESC
    )
    INSERT INTO HumanResources.EmployeePayHistory
    SELECT
     BusinessEntityID
    ,DATEADD(d,365,RateChangeDate)
    ,(Rate * 0.1) + Rate
    ,PayFrequency
    ,GETDATE()
    FROM LatestEmployeePay
    

    以前と同じ例ですが、今回はTOPが指定されていないことに注意してください。エラーを確認してください:

    列の数が列リストの定義と同じではありません

    Karlは同じ再帰CTEの例を使用しましたが、アンカーメンバーの列を取り出しました:

    WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
    (  
        SELECT HigherRankID, CrewID
        FROM #EnterpriseDSeniorOfficers
        WHERE HigherRankID IS NULL  
        UNION ALL  
        SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
        FROM #EnterpriseDSeniorOfficers AS e  
            INNER JOIN DirectReports AS d  
            ON e.HigherRankID = d.CrewID   
    )  
    SELECT HigherRankID, CrewID, Title, CrewLevel   
    FROM DirectReports  
    ORDER BY HigherRankID;
    

    上記のコードは、外部フォームを持つ3列のリストを使用していますが、アンカーメンバーには2列しかありませんでした。それは許可されていません。このような間違いをすると、エラーが発生します:

    CTEで許可されていないその他の事項

    上記のリストとは別に、SQLCTEで誤って使用した場合にエラーをトリガーするKarlの調査結果をいくつか示します。

    • SELECT INTO、OPTION句をクエリヒントとともに使用し、FORBROWSEを使用します。
    • 再帰メンバー列と比較したアンカーメンバー列のデータとタイプが異なります。
    • 再帰CTEの再帰メンバーに次のキーワードがある:
      • TOP
      • OUTER JOIN(ただし、INNER JOINは許可されています)
      • GROUPBYとHAVING
      • サブクエリ
      • SELECT DISTINCT
    • スカラー集計の使用。
    • 再帰メンバーでのサブクエリの使用。
    • SQLCTEをネストする。

    SQLCTEと一時テーブルとサブクエリ

    場合によっては、サブクエリを使用してSQLCTEを書き換えることができます。また、パフォーマンス上の理由から、一時テーブルを使用してSQLCTEを分解できる場合もあります。他のクエリと同様に、実際の実行プランとSTATISTICS IOをチェックして、どのオプションを選択するかを知る必要があります。 SQL CTEは目に優しいかもしれませんが、パフォーマンスの壁にぶつかった場合は、別のオプションを使用してください 。他のオプションより速いオプションはありません。

    同じ結果をもたらすカールの論文からの3つのクエリを調べてみましょう。 1つはSQLCTEを使用し、もう1つはサブクエリを使用し、3つ目は一時テーブルを使用します。簡単にするために、カールは小さな結果セットを使用しました。

    コードと結果セット

    複数のSQLCTEを使用することから始めました。

    WITH LatestEmployeePay
    AS
    (
        SELECT TOP 1
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        FROM HumanResources.EmployeePayHistory eph 
        WHERE eph.BusinessEntityID = 16
        ORDER BY eph.RateChangeDate DESC
    ),
    PreviousEmployeePay AS
    (
        SELECT TOP 1
         eph.BusinessEntityID
        ,eph.RateChangeDate
        ,eph.Rate
        FROM HumanResources.EmployeePayHistory eph
        INNER JOIN LatestEmployeePay lep 
            ON eph.BusinessEntityID = lep.BusinessEntityID
        WHERE eph.BusinessEntityID = 16
            AND eph.RateChangeDate < lep.RateChangeDate
        ORDER BY eph.RateChangeDate DESC
    )
    SELECT
     a.BusinessEntityID
    ,a.Rate
    ,a.RateChangeDate
    ,b.Rate AS PreviousRate
    FROM LatestEmployeePay a
    INNER JOIN PreviousEmployeePay b 
        ON a.BusinessEntityID = b.BusinessEntityID
    

    次はサブクエリです。お気づきのとおり、CTEはモジュール式で読みやすいように見えますが、以下のサブクエリは短くなっています。

    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.Rate
    ,eph.RateChangeDate
    ,(SELECT TOP 1 eph1.Rate FROM HumanResources.EmployeePayHistory eph1
      WHERE eph1.BusinessEntityID=16
        AND eph1.RateChangeDate < eph.RateChangeDate
      ORDER BY eph1.RateChangeDate DESC) AS PreviousRate
    FROM HumanResources.EmployeePayHistory eph
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC;
    

    Karlはまた、それをコードの小さなチャンクに分割し、一時テーブルを使用して結果を結合しようとしました。

    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    INTO #LatestPay
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
    
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    INTO #PreviousPay
    FROM HumanResources.EmployeePayHistory eph
    INNER JOIN #LatestPay lep 
        ON eph.BusinessEntityID = lep.BusinessEntityID
    WHERE eph.BusinessEntityID = 16
        AND eph.RateChangeDate < lep.RateChangeDate
    ORDER BY eph.RateChangeDate DESC
    
    SELECT
     a.BusinessEntityID
    ,a.Rate
    ,a.RateChangeDate
    ,b.Rate AS PreviousRate
    FROM #LatestPay a
    INNER JOIN #PreviousPay b 
        ON a.BusinessEntityID = b.BusinessEntityID
    
    DROP TABLE #LatestPay
    DROP TABLE #PreviousPay
    

    従業員16の現在および以前の支払いを取得したこれらの3つの方法の結果を見てください。これらは同じです:

    論理読み取り

    SQL Serverのリソースを最も消費するものは何ですか? STATISTICSIOを見てみましょう。 Karlはstatisticsparser.comを使用して、結果を適切にフォーマットしました。これは私たちにとって良いことです。

    図7は、CTEを使用する場合とサブクエリを使用する場合の論理的な読み取りを示しています。

    次に、一時テーブルを使用してコードを小さなチャンクに分割するときの論理読み取りの合計を確認します。

    では、何がより多くのリソースを消費するのでしょうか?カールは明確にするためにそれらをランク付けしました。

    1. サブクエリ– 4つの論理読み取り(WINNER!)。
    2. SQL CTE –6つの論理読み取り。
    3. 一時テーブル–8つの論理読み取り。

    この例では、最速はサブクエリになります。

    実際の実行計画

    STATISTICS IOから取得した論理読み取りを理解するために、Karlは実際の実行プランもチェックしました。彼はCTEから始めました:

    この計画からいくつかのことがわかります:

    • LatestEmployeePay CTEは、外部クエリで使用されたときと、 PreviousEmployeePay に結合されたときに、2回評価されました。 。したがって、これには2つのTOPノードが表示されます。
    • PreviousEmployeePayが表示されます 一度評価しました。

    次に、サブクエリを使用してクエリの実際の実行プランを確認します。

    ここには明らかなことがいくつかあります:

    • 計画はより単純です。
    • 最新の支払いを取得するためのサブクエリは1回だけ評価されるため、より簡単です。
    • CTEを使用したクエリの論理読み取りと比較して論理読み取りが少ないのも不思議ではありません。

    最後に、Karlが一時テーブルを使用したときの実際の実行プランは次のとおりです。

    これは3つのステートメントのバッチであるため、計画には3つの図も表示されます。 3つすべてが単純ですが、集合プランはサブクエリを使用したクエリのプランほど単純ではありません。

    時間統計

    dbForge Studio for SQL Serverソリューションを使用すると、QueryProfilerで時間統計を比較できます。そのためには、Ctrlキーを押しながら、実行履歴内の各クエリの結果名をクリックします。

    時間統計は、論理読み取りおよび実際の実行計画と一致しています。サブクエリは最速(88ms)で実行されました。その後、CTE(199ms)が続きます。最後は一時テーブル(536ms)の使用です。

    では、カールから何を学びましたか?

    この特定の例では、最速のオプションが必要な場合にサブクエリを使用する方がはるかに優れていることがわかりました。ただし、一連の要件がそのようなものでない場合は、別の話になる可能性があります。

    STATISTICS IOと実際の実行計画を常に確認して、使用する手法を確認してください。

    要点

    CTE(Common Table Expressions)が何であるかを理解するために、頭を壁にぶつけないでください。そうしないと、CTE(慢性外傷性脳症)を発症する可能性があります。冗談はさておき、私たちは何を明らかにしましたか?

    • 一般的なテーブル式は、一時的に結果セットと呼ばれます。動作とスコープはサブクエリに近いですが、より明確でモジュール化されています。名前もあります。
    • SQL CTEはコードを単純化するためのものであり、クエリを高速化するためのものではありません。
    • 私たちが学んだ7つのことは、SQLCTEで機能します。
    • 5つのことがエラーを引き起こします。
    • また、STATISTICSIOとActualExecution Planにより、常により適切な判断が得られることを確認しました。 CTEを、一時テーブルを使用してサブクエリおよびバッチと比較する場合は、これが当てはまります。

    楽しかった?次に、ソーシャルメディアボタンが押されるのを待っています。お気に入りのものを選んで、愛を分かち合いましょう!

    また読む

    CTEが複雑で強力なクエリの作成をどのように支援できるか:パフォーマンスの観点


    1. SQLiteAssetHelper:書き込み用にデータベースを開くことができませんでした(読み取り専用を試みます)

    2. MySQLに対するMySQLiの利点

    3. トップ30の最も有用な並行マネージャークエリ

    4. ソース管理からのデータベースの展開