ほとんどの句で列エイリアスの使用を許可しないような方法でクエリが解析されるのはなぜですか?


16

クエリを記述しようとすると、SQL Serverはクエリを実行するときにSELECTを解析する前に、クエリ内のWHEREを解析する(難しい方法)ことがわかりました。

MSDNドキュメントは、一般的な論理解析順序はSELECTは(したがって、「そのようなオブジェクト[別名」エラーは他の句の列別名を使用しようとしない場合に得られた)、ほぼ最後に解析されるようなものであると言います。エイリアスをどこでも使用できるようにする提案もありましたが、MicrosoftチームはANSI規格への準拠の問題を挙げてこれを打ち倒しました(この動作はANSI規格の一部であることを示唆しています)。

プログラマー(DBAではない)として、この動作がやや紛らわしいことに気づきました。列エイリアスを持つという目的を大幅に無効にしているように思われるためです(少なくとも、列エイリアスは、実際にエイリアスを使用できる場所はORDER BY のみであるため、クエリの実行の早い段階で解析されます)。プログラマーとしては、クエリをより強力で便利に、そしてDRYにする大きな機会を逃しているようです。

それは理にかなっているほど明白な問題のように見えますが、SELECTとORDER BY以外では列エイリアスを許可しないことを決定する他の理由がありますが、それらの理由は何ですか?

回答:


19

概要

論理的な理由はありません 行うができなかったはありませんが、利点は小さく、すぐには明らかにならないかもしれない落とし穴がいくつかあります。

調査結果

私はいくつかの研究を行い、いくつかの良い情報を見つけました。以下は、2012-08-09 17:49 GMTの信頼できるプライマリソース(匿名を希望する)からの直接の引用です。

SQLが最初に発明されたとき、SELECT句にはエイリアスがありませんでした。これは深刻な欠点であり、1986年頃にANSIによって言語が標準化されたときに修正されました。

この言語は、「非手続き型」、つまり、検索方法を指定せずに必要なデータを記述することを目的としていました。したがって、私が知る限り、SQL実装がクエリ全体を処理する前に解析できず、エイリアスをどこでも定義してどこでも使用できるようにする理由はありません。たとえば、次のクエリが有効でない理由はわかりません。

select name, salary + bonus as pay
from employee
where pay > 100000

これは妥当なクエリだと思いますが、一部のSQLベースのシステムでは、実装に関連した何らかの理由でエイリアスの使用に制限が導入される場合があります。SQL Serverがこれを行っていると聞いても驚くことではありません。

SQL-86標準と、なぜ最新のDBMSがエイリアスの再利用をサポートしていないのかをさらに研究することに興味がありますが、まだそれを使用する時間はありません。手始めに、ドキュメンテーションの入手先や、委員会を正確に構成した人物を見つける方法はわかりません。誰でも手伝うことができますか?また、SQL Serverの元となったSybase製品についても詳しく知りたいと思います。

この調査といくつかのさらなる考えから、他の句でエイリアスを使用することは可能ですが、他の言語機能に比べてDBMSメーカーにとってそれほど高い優先事項ではなかったのではないかと思いました。それほど大きな障害ではないため、クエリライターが簡単に回避できるため、他の進歩よりも努力するのは最適ではありません。さらに、それは明らかにSQL標準の一部ではないので独自のものとなります(確かにそれについてさらに調べるのを待っていますが)、したがって、DBMS間のSQL互換性を壊して、わずかな改善になります。比較すると、CROSS APPLY(これは実際には外部参照を許可する派生テーブルにすぎません)は大きな変更ですが、プロプライエタリは他の方法では簡単に実行できない信じられないほどの表現力を提供します。

どこでもエイリアスを使用する際の問題

SELECT項目をWHERE句に入れることを許可すると、クエリの複雑さ(したがって、適切な実行プランを見つけることの複雑さ)を爆発させるだけでなく、完全に非論理的なものを見つけることができます。試してください:

SELECT X + 5 Y FROM MyTable WHERE Y = X

MyTableにすでに列Yがあり、WHERE句が参照している場合はどうなりますか?解決策は、CTEまたは派生テーブルを使用することです。ほとんどの場合、追加費用はかかりませんが、同じ最終結果が得られます。CTEと派生テーブルは、エイリアスを1回だけ使用できるようにすることで、少なくともあいまいさの解決を強制します。

また、FROM句でエイリアスを使用しないことは非常に理にかなっています。これはできません:

SELECT
   T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
FROM
   Table1 T
   INNER JOIN Table2 T2
      ON T2.ID = CalcID
   INNER JOIN Table3 T3
      ON T2.ID = T3.ID

それは、(T2は密かにそのテーブルがリストに参加して提示されている前に、T3からの値を参照しているという意味で)、および循環参照ですくそ見えにくいです。これはどう:

INSERT dbo.FinalTransaction
SELECT
   newid() FinalTransactionGUID,
   'GUID is: ' + Convert(varchar(50), FinalTransactionGUID) TextGUID,
   T.*
FROM
   dbo.MyTable T

newid()関数が実行プランに2回配置され、まったく予期せずに2つの列に異なる値が表示されることに、どのくらい賭けたいですか?上記のクエリがCTEまたは派生テーブルのNレベルの深さで使用される場合はどうですか。私はあなたが想像できるよりも問題が悪いことを保証します。クエリ代数を適切に表現しているため、すでに物事は一度だけ、またはクエリプランのどの時点で評価されている場合について真剣に矛盾の問題は、とMicrosoftは述べている、それは修正されませんそれらの一部があります。予期しない結果が得られた場合、クエリを部分に分割します。連鎖参照を許可し、潜在的に非常に長い連鎖を介して循環参照を検出します。これらは非常に難しい問題です。並列処理を導入すると、作成に悪夢が生じます。

注:WHEREまたはGROUP BYでエイリアスを使用しても、newid()やrand()などの関数に関する問題に違いはありません。

再利用可能な式を作成するSQL Serverの方法

CROSS APPLY / OUTER APPLYは、SQL Serverでクエリの他の場所で使用できる式を作成する1つの方法です(FROM句の前ではありません)。

SELECT
   X.CalcID
FROM
   Table1 T
   INNER JOIN Table3 T3
      ON T.ID = T3.ID
   CROSS APPLY (
      SELECT
         T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
   ) X
   INNER JOIN Table2 T2
      ON T2.ID = X.CalcID

これは2つのことを行います。

  1. CROSS APPLYのすべての式が「名前空間」(ここではテーブルエイリアス、X)を取得し、その名前空間内で一意になるようにします。
  2. CalcIDがXから来ていることだけでなく、Xがまだ導入されていないため、テーブルT1とT3を結合するときにXから何も使用できない理由を明らかにします。

私は実際に、クロス適用が非常に好きです。それは私の忠実な友人になりました、そして、私はいつもそれを使います。部分的なUNPIVOT(ネイティブ構文を使用したPIVOT / UNPIVOTまたはUNPIVOT / PIVOTが必要)が必要ですか?CROSS APPLYで完了。何度も再利用される計算値が必要ですか?できた リンクサーバー経由の呼び出しの実行順序を厳密に強制する必要がありますか?速度の叫び声の改善で完了。1つのタイプの行のみを2行に分割する必要がありますか、または追加条件が必要ですか?できた

少なくとも、DBMS SQL Server 2005以降では、苦情の原因はこれ以上ありません。CROSSAPPLYは、あなたが望む方法で乾燥させる方法です。


14

正確な理由を説明することはできませんが、繰り返しを回避するために、CTE、サブクエリ、派生テーブルなどを使用するなど、繰り返し式に対する回避策があることを説明します。

繰り返し式を使用してクエリを表示する場合、式が1回だけリストされるように、クエリを書き直す方法を示すことができます。ただし、これによりクエリの書き込み/読み取りの複雑さが軽減されるだけで、効率についてはほとんど変わりません。SQL Serverは一般に、式が繰り返されていることを認識することについて非常に優れており、その作業を2回実行しません。逆の場合もありますが、実際にこの現象が発生するのを観察するときのみ、効率を考慮する必要があります。あなたが書いた表現のほとんどは、実際には計画内の1つの操作にしか折りたたまれていないと思います。

それはすべて、この質問からの答えの一部を繰り返します:

/dba/19762/why-is-the-select-clause-listed-first


これは、標準に従ってクエリが処理される方法に関するJoe Celkoの説明です(おそらく、Celkoによるニュースグループの投稿から引用を盗んだaspfaq.comの記事からこれを盗みました)。

SELECTがSQLでどのように機能するかを以下に示します(少なくとも理論上は)。実際の製品は、可能なときに物事を最適化します。

FROM句から開始し、すべての結合、共用体、交差、およびその他のテーブルコンストラクターから作業テーブルを構築します。ASオプションを使用すると、この作業テーブルに名前を付けて、残りのクエリに使用する必要があります。

WHERE句に移動し、条件を満たさない行を削除します。つまり、TRUEをテストしません(UNKNOWNおよびFALSEを拒否します)。WHERE句は、FROM句での作業に適用されます。

オプションのGROUP BY句に移動して、グループを作成し、各グループを単一の行に減らして、元の作業テーブルを新しいグループ化されたテーブルに置き換えます。グループ化されたテーブルの行は、グループの特性である必要があります。(1)グループ化列(2)グループに関する統計(集計関数)(3)関数または(4)これら3つの項目で構成される式。

オプションのHAVING句に移動し、グループ化された作業テーブルに適用します。GROUP BY句がなかった場合、テーブル全体を1つのグループとして扱います。

SELECT句に移動して、リスト内の式を作成します。これは、SELECT内のスカラーサブクエリ、関数呼び出し、および式が、他のすべての句が実行された後に実行されることを意味します。AS演算子は、SELECTリスト内の式にも名前を付けることができます。これらの新しい名前は一度に存在しますが、WHERE句が実行された後です。そのため、それらをSELECTリストまたはWHERE cluaseで使用することはできません。

ネストされたクエリ式は、C、Pascal、Algolなどのブロック構造化言語に期待される通常のスコープ規則に従います。つまり、最も内側のクエリは、それらが含まれるクエリの列とテーブルを参照できます。

これは、SELECTがGROUP BYよりも多くの列を持つことができないことを意味します。ただし、列の数を減らすことができます。

現在、Celkoは、標準の以前のバージョンの主要な貢献者の1つでした。WHY?投機を除いて、あなたが質問に対する決定的な答えを得るかどうかはわかりません。私の推測では、最初に実際の操作をリストすることで、パーサーが操作のタイプがどのようなものになるかを正確に知ることが非常に簡単になります。最終的にSELECTorまたはUPDATEor DELETEになる可能性のある20テーブルの結合を想像してください。これらのエンジンのコードは元々、文字列の解析に非常にコストがかかった時代に書き戻されたことを思い出してください。

SQL標準FROMが最初に指示されている場合、ベンダーは独自に文法を別の順序で解析することを決定した可能性があるため、記述された句の順序を100%の処理順序に完全に従うと期待することは依然として意味をなさない可能性があることに注意してください時間。

同じようなことが当てはまりますCASEこのサイトでは、たとえば、CASE常に順番に処理して短絡するという以前から信じられていた神話が間違っているというシナリオをここで見ました。これは、SQL Serverが記述された順序で結合を評価する、左から右に句を短絡WHEREする、CTEを1回または特定の順序で複数回参照されている場合でも処理するなど、他の一般的な信念にも拡張されます。製品は、クエリが宣言的に機能するように指定した方法を正確に反映していなくても、適合性がどのように見えるかを自由に最適化できます。


2
また、クエリのさまざまな部分でエイリアスを使用するかどうかは、オプティマイザーや実行エンジンではなく、パーサーによって強制されることに注意してください。エンジンが実際にクエリを実行する方法は、必ずしも構文に影響する制限を反映していません。
アーロンバートランド

2

ではエンティティSQL、あなたは、いくつかの状況では、クエリ内の他の場所で式の別名を使用することができます。

select k1, count(t.a), sum(t.a)
from T as t
group by t.b + t.c as k1

ここでは、GROUP BY句で式を使用するために、句で式を定義する必要があることに注意してくださいSELECT

SQLクエリでこの種のエイリアスとしての再利用可能式を許可することは明らかに可能です。

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