問題
サブクエリで外部テーブルを参照できる場合と、それが不適切な要求である場合(およびその理由)に関するルールをよりよく理解する必要があります。リファクタリングしようとしているOracleSQLクエリで重複を発見しましたが、参照テーブルをグループ化されたサブクエリに変換しようとすると問題が発生します。
次のステートメントは適切に機能します。
SELECT t1.*
FROM table1 t1,
INNER JOIN table2 t2
on t1.id = t2.id
and t2.date = (SELECT max(date)
FROM table2
WHERE id = t1.id) --This subquery has access to t1
残念ながら、table2には重複するレコードがあることがあるため、t1に結合する前にまずt2を集約する必要があります。ただし、この操作を実行するためにサブクエリでラップしようとすると、突然SQLエンジンが外部テーブルを認識できなくなります。
SELECT t1.*
FROM table1 t1,
INNER JOIN (SELECT *
FROM table2 t2
WHERE t1.id = t2.id --This loses access to t1
and t2.date = (SELECT max(date)
FROM table2
WHERE id = t1.id)) sub on t1.id = sub.id
--Subquery loses access to t1
これらは基本的に異なるクエリであり、コンパイラにまとめてもらいますが、一方が機能するのに他方が機能しない理由がわかりません。
サブクエリでテーブル参照を複製し、サブクエリを外部テーブルから効果的に切り離すことができることはわかっていますが、これはこのタスクを実行するための非常に醜い方法のようです(コードと処理のすべての重複がある場合)。
役立つ参考資料
SQL Serverで句が実行される順序のこの素晴らしい説明を見つけました:( INNER JOIN ON vs WHERE句 )。私はOracleを使用していますが、これは全面的に標準であると思います。条項の評価には明確な順序があり(FROMが最初)、リストのさらに下にある条項は、以前に処理されたすべての情報にアクセスできると思います。 2番目のクエリが何らかの理由でその順序を変更して、サブクエリの評価が早すぎると想定することしかできませんか?
さらに、同様の質問がありました( サブクエリで外部クエリのテーブルを参照 )が、入力は良好でしたが、彼が行っていることを実行できない理由を実際に説明することはなく、代替ソリューションを提供しました彼の問題に。私は彼らの代替ソリューションを試しましたが、それは私に他の問題を引き起こしています。つまり、日付参照を使用したサブクエリは操作全体の基本であるため、削除することはできません。
質問
ここで行ったことを理解したいのですが...ステートメント全体をサブクエリでラップした後ではなく、最初のサブクエリで外部テーブルを表示できるのはなぜですか?
そうは言っても、私がやろうとしていることができない場合、重複を排除するために最初のクエリをリファクタリングする最良の方法は何ですか? table1を2回参照する必要がありますか(必要なすべての複製を含む)?または、(おそらく)この問題に取り組むためのより良い方法はありますか?
前もって感謝します!
------編集------
上記のこれらのクエリは、実際にリファクタリングしているクエリではなく、発生している問題の例であると推測する人もいます。私が扱っているクエリははるかに複雑なので、人々が軌道に乗らないのではないかと心配しているので、ここに投稿するのをためらっています。
------更新------
それで私は仲間の開発者によってこれを実行しました、そして彼は私のサブクエリがなぜt1へのアクセスを失っているのかについて1つの可能な説明をしました。このサブクエリを括弧で囲んでいるので、彼は、テーブルt1が評価される前にこのサブクエリが評価されていると考えています。これは間違いなく「ORA-00904: "t1"。 "id":無効な識別子」エラーを説明します。また、演算の算術順序と同様に、ステートメントに親を追加すると、特定の句の評価内で優先順位が与えられることも示唆されます。私がここで見ているものの論理的な説明である、専門家が賛成/反対する場合、私はまだ専門家が検討することを望んでいます。
それで、マーティン・スミスが上で行ったコメント(THANKS MARTIN!)に基づいてこれを理解し、この問題に遭遇した他の人に私の発見を共有したかったのです。
技術的な考慮事項
まず、問題を説明するために適切な用語を使用すると、確かに役立ちます。上記の最初のステートメントでは、相関サブクエリを使用しています。
これは、外部テーブルのすべての行に対してサブクエリを再実行するため、実際にはデータをプルバックするかなり非効率的な方法です。このため、コードでこれらのタイプのサブクエリを削除する方法を探します。
一方、私の2番目のステートメントは、Oracleではインラインビューと呼ばれるものを使用していました。SQLServerでは派生テーブルとも呼ばれます。
インラインビュー/派生テーブルは、クエリの開始時に名前のない一時的なビューを作成し、操作が完了するまでそれを別のテーブルのように扱います。コンパイラはFROM行でこれらのサブクエリを検出したときに一時ビューを作成する必要があるため、これらのサブクエリは完全に自己完結型であり、サブクエリの外部への参照はありません。
なぜ私がやっていることは愚かだったのか
その2番目のテーブルで私がやろうとしていたことは、基本的に、私のステートメントの知識の範囲外である別のテーブルへのあいまいな参照に基づいてビューを作成することでした。これは、クエリで明示的に指定していないテーブルのフィールドを参照しようとするようなものです。
回避策
最後に、マーティンが私がやろうとしていたことを達成するためにかなり賢いが最終的には非効率的な方法を提案したことは注目に値します。 Applyステートメントは独自のSQLServer関数ですが、派生テーブルの外部のオブジェクトと通信できます。
同様に、この機能はOracleでさまざまな構文を使用して使用できます。
最終的には、このクエリへのアプローチ全体を再評価します。つまり、最初から再構築する必要があります(信じられないかもしれませんが、この怪物を最初に作成しなかったのですが、誓います!)。 コメントしてくれたすべての人に感謝します-これは間違いなく私を困惑させましたが、すべての入力は私を正しい軌道に乗せるのに役立ちました!
次のクエリはどうですか。
SELECT t1.* FROM
(
SELECT *
FROM
(
SELECT t2.id,
RANK() OVER (PARTITION BY t2.id, t2.date ORDER BY t2.date DESC) AS R
FROM table2 t2
)
WHERE R = 1
) sub
INNER JOIN table1 t1
ON t1.id = sub.id