web-dev-qa-db-ja.com

選択が行を返さないときに代替選択を使用するCTE

再帰なしで、関連するCTEを作成しましたが、要件のビジネスロジックを処理するときに複数の選択が行われます。ただし、行が返されないシナリオに必要なロジックではなく、真ん中のCTEs選択の1つから見つかったすべての行を返す必要があるとアドバイスされています最終操作の代わりに。

どうすればこれを達成できますか?

次の例は私のプロセスの基本的な解釈であり、問​​題の操作は複数のカスケードされたCTE選択が本質的に一緒にチェーンされた後に発生します。


(- SQLFiddle にあります)

ユーザーテーブルには、UserIdsのIDが12の2つのレコードがあります。最初のCTEs選択、TargetUsersは3番目のCTE ComputeWithUsersAllUsersを代わりに使用する必要がある行を返しません。

WITH TargetUsers AS
(
  SELECT * 
  FROM Users
  WHERE UserId > 5
)
, AllUsers AS
(
   SELECT * FROM Users
)
, ComputeWithUsers
(
-- This CTE will determine it needs to use `AllUsers` and not `TargetUsers`
)

または、メインの操作が行を返さないときにすべてのユーザーを抽出するのに十分なTargetUsers selectをスマートにする方法はありますか?

2
ΩmegaMan

これを行う方法は次のとおりです。「1つのクエリですべて実行する」というのは、無意味な人為的な要件ではないと想定しています。

SELECT cols INTO #x FROM dbo.Users WHERE UserID > 5;

IF @@ROWCOUNT > 0
  SELECT cols FROM #x;
ELSE
  SELECT cols FROM dbo.Users;

「すべてを1つのクエリで実行する」ことが困難な要件であり、#tempテーブルを使用できない場合、または複雑なロジックがあり、私たちからだまされたり、繰り返したくない場合は、パフォーマンスの選択肢にお金を払わなければならないのではないかと思います。これは非常に効率的であるように見えますが、ほとんどの人が期待する/期待するよりも、ベーステーブルへのヒット数が多くなります。

;WITH TargetUsers AS 
(
  SELECT cols FROM dbo.Users WHERE UserID > 5
),
AllUsers AS 
(
  SELECT cols FROM dbo.Users
  WHERE NOT EXISTS (SELECT 1 FROM TargetUsers)
    -- this will only return rows if the first CTE is empty
),
ComputeWithUsers AS
(
  SELECT cols FROM TargetUsers 
  UNION ALL 
  SELECT cols FROM AllUsers
)
...;

実際、UserIDが主キーであり、フィルターが本当にUserIDにある場合は、次のようにするとよいでしょう(ただし、さまざまな要因によって、計画を確認する必要があります)。

;WITH TargetUserIDs AS 
(
  SELECT UserID FROM dbo.Users WHERE UserID > 5
),
AllUserIDs AS 
(
  SELECT UserID FROM dbo.Users
  WHERE NOT EXISTS (SELECT 1 FROM TargetUsers)
    -- this will only return rows if the first CTE is empty
),
ComputeWithUsers AS
(
  SELECT UserID FROM TargetUserIDs 
  UNION ALL 
  SELECT UserID FROM AllUserIDs
)
...
-- and join to dbo.Users to get other columns necessary
-- deferring that to as late as possible

うまくいけば、クエリは簡素化のために最適化されていないプラクティスを使用しており、本番環境ではそうではありませんが、これらを読んでください:

4
Aaron Bertrand

パフォーマンスが許容範囲内であると想定すると、最初のフィルターが結果を返せなかった場合にすべてのユーザーを取得する手段として、以下を使用できます。

SELECT *
FROM Users
WHERE UserID > 5
OR NOT EXISTS (SELECT * FROM Users WHERE UserID > 5)

あなたは単に工夫したORを設定しているだけです。これにより、ベース選択が何も返さない場合にすべてのレコードがフィルターを通過できるようになります。

3
Dave