共通テーブル式(CTE)を使用する場合


230

共通テーブル式について読み始めたが、それらを使用する必要があるユースケースを考えることができない。派生テーブルでも同じことができるので、冗長であると思われます。足りないものやよくわからないものはありますか?CTEのケースを作成するために、通常の選択、派生、または一時テーブルクエリの制限の簡単な例を誰かに教えてもらえますか?簡単な例をいただければ幸いです。

回答:


197

1つの例として、同じデータセットを複数回参照/結合する必要がある場合は、CTEを定義することで実行できます。したがって、これはコードの再利用の一形態になる可能性があります。

自己参照の例は再帰です:CTEを使用した再帰クエリ

Books Onlineから取られたエキサイティングなMicrosoftの定義の場合

CTEは次の目的で使用できます。

  • 再帰クエリを作成します。詳細については、「共通テーブル式を使用した再帰クエリ」を参照してください

  • ビューの一般的な使用が不要な場合は、ビューを置き換えます。つまり、定義をメタデータに保存する必要はありません。

  • スカラー副選択から派生した列によるグループ化、または確定的でないか外部アクセスが可能な関数によるグループ化を有効にします。

  • 同じステートメントで、結果のテーブルを複数回参照します。


7
うん。派生テーブルを自己結合することはできません。ただし、CTEへの自己参加は、2つの個別の呼び出しが残っていることを強調しておきます。
マーティン・スミス、

@Martin-私は驚いています。その声明をバックアップできますか?
RichardTheKiwi

@Johnありがとう、私は4guysfromrolla.com/webtech/071906-1.shtmlも非常に有用であることを発見しています
imak

4
@cyberkiwi-どのビット?自己結合が2つの異なる呼び出しにつながるということですか?この回答の例を参照してくださいstackoverflow.com/questions/3362043/...
マーティン・スミス

4
CTEに関する興味深い事実。CTEが複数回参照されると、なぜCTEのNEWID()が変更されるのか、いつも疑問に思いました。select top 100 * into #tmp from master..spt_values order by 1,2,3,4 select A.number, COUNT(*) from #tmp A inner join #tmp B ON A.number = B.number+1 group by A.numbervswith CTE AS (select top 100 * from master..spt_values order by 1,2,3,4) select A.number, COUNT(*) from CTE A inner join CTE B ON A.number = B.number+1 group by A.number
RichardTheKiwi

50

それらを使用して、複雑なクエリ、特に複雑な結合とサブクエリを分割します。クエリの意図を理解するのに役立つように、それらを「疑似ビュー」としてますます使用しています。

それらについての私の唯一の不満はそれらが再利用できないことです。たとえば、同じCTEを使用できる2つの更新ステートメントを含むストアドプロシージャがあるとします。ただし、CTEの「範囲」は最初のクエリのみです。

問題は、「簡単な例」ではおそらくCTEは実際には必要ないということです。

それでも、非常に便利です。


OK。この概念の周りに私の頭を助けることができるいくつかの比較的複雑な例でケースを作ることができますか?
イマク2011年

28
「それらについての私の唯一の不満は、それらが再利用できないことです」-再利用したいCTEは、VIEW:)の候補と見なされるべきです
1

6
@onedaywhen:わかりましたが、それは私が常に満足しているわけではないグローバルスコープを意味します。プロシージャの範囲内でCTEを定義し、それを選択と更新、または異なるテーブルからの類似データの選択に使用したい場合があります。
n8wrl 2013年

5
同じCTEが複数回必要な場合は、それを一時テーブルにフィードして、一時テーブルを必要なだけ使用します。
Fandango68 2016年

43

cteを使用する理由は2つあります。

where句で計算値を使用する。これは、派生テーブルよりも少しクリーンに思えます。

2つのテーブルがあるとします-Questions.ID = Answers.Question_Id(およびクイズID)によって結合された質問と回答

WITH CTE AS
(
    Select Question_Text,
           (SELECT Count(*) FROM Answers A WHERE A.Question_ID = Q.ID) AS Number_Of_Answers
    FROM Questions Q
)
SELECT * FROM CTE
WHERE Number_Of_Answers > 0

質問と回答のリストを取得する別の例を次に示します。回答を結果の質問と一緒にグループ化してください。

WITH cte AS
(
    SELECT [Quiz_ID] 
      ,[ID] AS Question_Id
      ,null AS Answer_Id
          ,[Question_Text]
          ,null AS Answer
          ,1 AS Is_Question
    FROM [Questions]

    UNION ALL

    SELECT Q.[Quiz_ID]
      ,[Question_ID]
      ,A.[ID] AS  Answer_Id
      ,Q.Question_Text
          ,[Answer]
          ,0 AS Is_Question
        FROM [Answers] A INNER JOIN [Questions] Q ON Q.Quiz_ID = A.Quiz_ID AND Q.Id = A.Question_Id
)
SELECT 
    Quiz_Id,
    Question_Id,
    Is_Question,
    (CASE WHEN Answer IS NULL THEN Question_Text ELSE Answer END) as Name
FROM cte    
GROUP BY Quiz_Id, Question_Id, Answer_id, Question_Text, Answer, Is_Question 
order by Quiz_Id, Question_Id, Is_Question Desc, Name

10
最初の例を簡略化して、CTEの代わりにネストされたクエリを使用することはできませんか?
Sam

2
両方の例が考えられます。
Manachi

3
あなたはCTEなしで最初のものを追加するべきでした、そしてそれから後者がなぜ有用であるかはすぐに明らかです。
Ufos 2017年

HAVINGサブを使用して同様とすることができる後期フィルタを行うための別の方法ですSELECT
ウィリアム・エントリケン

21

CTEを使用すると便利だとわかったシナリオの1つは、1つ以上の列に基づいてDISTINCT行のデータを取得したいが、テーブル内のすべての列を返したい場合です。標準クエリでは、最初に個別の値を一時テーブルにダンプしてから、それらを元のテーブルに結合して残りの列を取得するか、結果を返すことができる非常に複雑なパーティションクエリを作成する必要がある場合があります。 1回実行されますが、ほとんどの場合、読み取り不可能でパフォーマンスの問題が発生します。

ただし、CTEを使用する(レコードの最初のインスタンスの選択で Tim Schmelterが回答)

WITH CTE AS(
    SELECT myTable.*
    , RN = ROW_NUMBER()OVER(PARTITION BY patientID ORDER BY ID)
    FROM myTable 
)
SELECT * FROM CTE
WHERE RN = 1

ご覧のとおり、これは読みやすく、保守しやすいものです。そして、他のクエリと比較して、パフォーマンスがはるかに優れています。


16

おそらく、CTEを単一のクエリに使用されるビューの代わりとして考える方がより意味があります。ただし、オーバーヘッド、メタデータ、または正式なビューの永続性は必要ありません。次のような場合に非常に役立ちます。

  • 再帰クエリを作成します。
  • クエリでCTEの結果セットを複数回使用します。
  • 同一のサブクエリの大きなチャンクを削減することにより、クエリをわかりやすくします。
  • CTEの結果セットで派生した列によるグループ化を有効にする

これは、カットアンドペーストの例です。

WITH [cte_example] AS (
SELECT 1 AS [myNum], 'a num' as [label]
UNION ALL
SELECT [myNum]+1,[label]
FROM [cte_example]
WHERE [myNum] <=  10
)
SELECT * FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_all' FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_odd' FROM [cte_example] WHERE [myNum] % 2 = 1
UNION
SELECT SUM([myNum]), 'sum_even' FROM [cte_example] WHERE [myNum] % 2 = 0;

楽しい


7

今日は、SQL Server 2005で導入され、以降のバージョンでも利用できる新機能である共通テーブル式について学習します。

共通テーブル式:-共通テーブル式は、一時的な結果セット、つまりSQL Serverのビューの代替として定義できます。共通テーブル式は、それが定義されたステートメントのバッチでのみ有効であり、他のセッションでは使用できません。

CTE(共通テーブル式)を宣言する構文:-

with [Name of CTE]
as
(
Body of common table expression
)

例を挙げましょう:-

CREATE TABLE Employee([EID] [int] IDENTITY(10,5) NOT NULL,[Name] [varchar](50) NULL)

insert into Employee(Name) values('Neeraj')
insert into Employee(Name) values('dheeraj')
insert into Employee(Name) values('shayam')
insert into Employee(Name) values('vikas')
insert into Employee(Name) values('raj')

CREATE TABLE DEPT(EID INT,DEPTNAME VARCHAR(100))
insert into dept values(10,'IT')
insert into dept values(15,'Finance')
insert into dept values(20,'Admin')
insert into dept values(25,'HR')
insert into dept values(10,'Payroll')

従業員と部署の2つのテーブルを作成し、各テーブルに5行挿入しました。次に、これらのテーブルを結合して、さらに使用するための一時的な結果セットを作成します。

With CTE_Example(EID,Name,DeptName)
as
(
select Employee.EID,Name,DeptName from Employee 
inner join DEPT on Employee.EID =DEPT.EID
)
select * from CTE_Example

ステートメントの各行を1つずつ理解してみましょう。

CTEを定義するには、「with」句を記述してから、テーブル式に名前を付けます。ここでは、「CTE_Example」という名前を付けています。

次に、 "As"を記述し、コードを2つの角かっこ(---)で囲みます。角かっこで複数のテーブルを結合できます。

最後の行では、「Select * from CTE_Example」を使用しました。コードの最後の行でCommonテーブル式を参照しているため、ビューのように定義でき、単一のビューを使用していると言えます。バッチとCTEは永続的なオブジェクトとしてデータベースに保存されません。しかし、それはビューのように動作します。CTEで削除および更新ステートメントを実行できます。これは、CTEで使用されている参照テーブルに直接影響します。この事実を理解するために例を見てみましょう。

With CTE_Example(EID,DeptName)
as
(
select EID,DeptName from DEPT 
)
delete from CTE_Example where EID=10 and DeptName ='Payroll'

上記のステートメントでは、CTE_Exampleから行を削除しており、CTEで使用されている参照テーブル "DEPT"からデータを削除します。


まだ要点はわかりません。これとまったく同じ条件でDEPTから削除するだけの違いは何ですか?それは何も簡単にはしないようです。
Holger Jakobs、2014年

私が間違っている場合は修正してください。ただし、実行計画は異なる場合があります。同じ目的を達成する方法はたくさんありますが、状況によっては他の方法よりも優れている点もあるとNeerajは言います。たとえば、状況によっては、DELETE FROMステートメントよりもCTEを読み取る方が簡単な場合があります。また、状況によってはその逆の場合もあります。パフォーマンスが向上または悪化する可能性があります。等
WonderWorker

7

「順序付けされた更新」を実行する場合に非常に便利です。

MS SQLでは、UPDATEでORDER BYを使用することはできませんが、CTEを使用すると、次のように実行できます。

WITH cte AS
(
    SELECT TOP(5000) message_compressed, message, exception_compressed, exception
    FROM logs
    WHERE Id >= 5519694 
    ORDER BY Id
)
UPDATE  cte
SET     message_compressed = COMPRESS(message), exception_compressed = COMPRESS(exception)

詳細はこちらをご覧ください:ms sqlを使用して更新および注文する方法


0

まだ指摘されていない1つのポイントは、速度です。私はそれが古い回答の質問であることを知っていますが、これは直接のコメント/回答に値すると思います:

派生テーブルでも同じことができるので、冗長であるように見える

初めてCTEを使用したとき、その速度にまったく驚かされました。教科書のようなケースで、CTEに非常に適していましたが、CTEを使用したすべての場面で、大幅な速度の向上がありました。最初のクエリは派生テーブルで複雑で、実行に長い時間がかかりました。CTEを使用すると、ほんの数秒しかかかりませんでした。


-4
 ;with cte as
  (
  Select Department, Max(salary) as MaxSalary
  from test
  group by department
  )  
  select t.* from test t join cte c on c.department=t.department 
  where t.salary=c.MaxSalary;

これを試して

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.