SQL を発明した本来の目的の 1 つは、データ クエリの計算を実装する際の困難を軽減することであることは明らかです。SQL では英語に似た語彙や文法が多く使用されており、技術者以外の人でも習得できることが期待されています。確かに、単純なSQLはプログラミング経験のない人でも英語として読むことができます。
ただし、やや複雑なクエリ計算要件に直面すると、SQL は無力に見え、多くの場合、マルチレベルのネストされたステートメントを数百行作成することになります。この種の SQL は、技術者以外の人が完了するのが難しいことは言うまでもなく、プロのプログラマーにとっても簡単ではなく、多くのソフトウェア会社のアプリケーション試験のハイライトになることがよくあります。3 行や 5 行の SQL は教科書や研修でしか存在せず、実際にはレポートのクエリに使用される SQL は通常「K」単位で測定されます。
SQL の問題の分析と議論
どうしてこれなの?非常に単純な例を使用して、計算に関する SQL の欠点を調べます。
3 つのフィールドを持つ販売実績テーブルを考えてみましょう (わかりやすくするために日付情報は省略しています)。
売上高 | 販売実績表 |
販売 | 営業担当者の名前(重複する名前がないことを前提) |
製品 | 販売中の製品 |
額 | この販売員によるこの商品の売上 |
次に、エアコンとテレビの両方を扱う販売員トップ 10 のリストを知りたいと思います。
この問題は難しいものではありません。人々は自然に次のような計算プロセスを設計します。
1. エアコンの売上順に並べ替えてトップ 10 を見つけます。
2. テレビの売上順に並べ替えてトップ 10 を見つけます。
3. 1 と 2 の結果の積を求めて答えを求めます。
今度は SQL でやってみましょう。
初期の SQL はステップをサポートしていないため、最初の 2 つのステップをサブクエリに記述するのは少し面倒です。
select * from
( select top 10 sales from sales_amount where product='AC' order by amount desc )
intersect
( select top 10 sales from sales_amount where product='TV' order by amount desc )
その後、SQL では段階的に実行しないと面倒であることが判明したため、with キーワードを使用して前の手順のクエリ結果に名前を付け、次の手順で使用できる CTE 構文が提供されました。
with A as
select top 10 sales from sales_amount where product='AC' order by amount desc
B as
select top 10 sales from sales_amount where product='TV' order by amount desc
select * from A intersect B
文は短くありませんが、ステップバイステップでアイデアがより明確になります。
では、問題をもう少し複雑にして、全商品の売上が上位 10 位以内にある販売員を計算するように変更してみましょう。 :
1. すべての製品をリストします。
2. 各製品のトップ 10 を取り出して個別に保存します。
3. 上位 10 位すべての交点を求めます。
ただし、CTE 構文を使用すると、特定の数の中間結果に対してのみ追加の計算を行うことができます。また、商品が合計で複数あることが事前に分からないため、WITHの句の数が不確かになるため、書き出すことができません。
別の考え方:
1. データを製品ごとにグループ化し、各グループを並べ替えて上位 10 位を取り出します。
2. 上位 10 位すべての交点を求めます。
ただし、これには最初のステップのグループ化結果を保存する必要があり、この中間結果はテーブルであり、対応するグループ化の上位 10 メンバーを格納するフィールドがあります。つまり、フィールドの値はセットになります。 SQL ではサポートされていません。このデータ型はまだ書き込むことができません。
窓関数のサポートがあれば、また考え方を変えることができます。製品ごとにグループ化した後、各販売員が全グループの上位 10 位に表示される回数を計算し、それが製品の総数と同じであれば、は、その販売員がすべての製品の売上の中でトップ 10 に入っていることを意味します。
select sales
from ( select sales,
from ( select sales,
rank() over (partition by product order by amount desc ) ranking
from sales_amount)
where ranking <=10 )
group by sales
having count(*)=(select count(distinct product) from sales_amount)
書くことはできますが、こんな複雑なSQLを書ける人がどれだけいるでしょうか?
最初の 2 つの単純なアイデアは SQL では実現できません。使用できるのは 3 番目の回りくどい考え方だけです。その理由は、SQL の重要な欠点である不完全なコレクションにあります。
SQL にはセットの概念がありますが、基本的なデータ型としてセットは提供されていません。変数またはフィールドの値をセットにすることはできず、テーブル以外にセット データ型がないため、大量のセット演算が行われます。 in the head 書くにも書くにも回り道は必要だ。
上記の計算ではキーワード top を使用しましたが、実際、関係代数理論にはそのようなものはありません (他の計算と組み合わせることができます)。これは SQL の標準的な記述方法ではありません。
トップなしでトップ10を見つけるのがどれほど難しいか見てみましょう?
一般的な考え方は次のとおりです。順位として自分より大きいメンバーの数を見つけ、順位が 10 を超えないメンバーを取り出し、次のような SQL を記述します。
select sales
from ( select A.sales sales, A.product product,
(select count(*)+1 from sales_amount
where A.product=product AND A.amount<=amount) ranking
from sales_amount A )
where product='AC' AND ranking<=10
また
select sales
from ( select A.sales sales, A.product product, count(*)+1 ranking
from sales_amount A, sales_amount B
where A.sales=B.sales and A.product=B.product AND A.amount<=B.amount
group by A.sales,A.product )
where product='AC' AND ranking<=10
プロのプログラマーにとって、このような SQL ステートメントを作成するのは簡単ではないかもしれません。代わりに、上位 10 位のみが計算されます。
一歩下がって考えると、上があっても前の部分が取り出しやすくなるだけです。6 位から 10 位を取るように問題を変更したり、次の販売者より 10% 多い売上を上げている営業マンを見つけたりした場合でも、これらの困難は依然として存在しており、SQL でそれを行うには依然として遠回りな考え方が必要です。
この現象の理由は、SQL のもう 1 つの重要な欠点、つまり順序付けされたサポートの欠如です。SQL は数学における順序なし集合を継承しているため、順序に関係する計算が直接的に非常に困難になり、順序に関係する計算がどの程度一般的になるか (先月との比較、前年同期との比較、上位 20%、ランキングなど)。
SQL2003 標準に追加されたウィンドウ関数は、順序に関連するいくつかの計算機能を提供します。これにより、上記の問題の一部がより簡単な解決策になり、SQL のこの問題がある程度軽減されます。ただし、ウィンドウ関数の使用にはサブクエリが伴うことが多く、ユーザーはコレクション メンバーへの順次アクセスを直接使用できません。また、解決が困難な順序付き操作が依然として多数存在します。
ここで注目したいのは、上で計算した「優秀な」営業マンの男女比、つまり男性と女性が何人いるかということです。通常の状況では、営業担当者の性別情報は業績表ではなく名簿に記録されますが、これは次のように簡略化されます。
職員 | 従業員フォーム |
名前 | 重複する名前がないことを前提とした従業員名 |
性別 | 従業員の性別 |
「優秀な」営業マンのリストはすでに計算済みです。より自然なアイデアは、名簿にアクセスするときにそのリストを使用して性別を調べ、再度数えることです。ただし、SQL でテーブル間で情報を取得するにはテーブル間の結合が必要なので、元の結果に従って SQL は次のように記述されます。
select employee.gender,count(*)
from employee,
( ( select top 10 sales from sales_amount where product='AC' order by amount desc )
intersect
( select top 10 sales from sales_amount where product='TV' order by amount desc ) ) A
where A.sales=employee.name
group by employee.gender
関連テーブルが 1 つ増えるだけで、このような煩雑さは生じますが、実際には、テーブル全体にかなり多くの情報が格納されており、複数のレイヤーが存在することもよくあります。たとえば、営業担当者には部門があり、その部門にはマネージャーがいます。今度は、どのマネージャーが「優秀な」営業担当者の責任を負っているのかを知りたいので、3 つのテーブルを接続する必要があります。どことを明確に記述するのは簡単ではありません。この計算ではグループ化されています。これは仕事です。
これは、次に説明したい SQL の重要な問題です。オブジェクト参照メカニズムが欠如しているため、リレーショナル代数におけるオブジェクト間の関係は、同じ外部キー値によって完全に維持されます。これは、外部キーを探すときに非効率であるだけでなく、外部キーを探すこともできません。外部キーによって使用される 指定されたレコード メンバーは、このレコードの属性として直接扱われます。上記の文が次のように記述できると想像してください。
select sales.gender,count(*)
from (…) // …是前面计算“好”销售员的SQL
group by sales.gender
明らかに、この文はより明確であるだけでなく、計算効率も高くなります (結合計算がありません)。
簡単な例を通して、SQL のいくつかの重要な問題点を分析しました。これが、SQL の記述が困難または長くなる主な理由でもあります。コンピューティング システムに基づいてビジネス上の問題を解決するプロセスは、ビジネス上の問題の解決策を正式なコンピューティング文法に変換するプロセスです(小学生が問題を解いて、問題を形式的な四則演算に変換するのと同じです)。SQL における上記の難しさは、問題解決の翻訳に大きな障害を引き起こし、極端な場合には、問題解決を計算文法に形式化する困難の方が、問題を解決する困難よりもはるかに大きいという奇妙な現象が発生します。それ自体。
プログラマーにとって理解しやすいもう 1 つの例は、SQL を使用してデータ計算を行うのは、アセンブリ言語を使用して四則演算を実行するのと似ているということです。3+5*7 のような数式を書くのは簡単ですが、アセンブリ言語を使用する場合 (X86 を例にします)、次のように書かなければなりません。
mov ax,3
mov bx,5
mul bx,7
add ax,bx
この種のコードは、書き込みと読み取りの両方において 3+5*7 よりもはるかに悪いです (小数が発生するとさらにひどいことになります)。熟練したプログラマーにとってはそれほど難しいことではありませんが、この書き方は多くの人にとってはまだわかりにくいものであり、その意味で FORTRAN は確かに偉大な発明です。
理解を容易にするために、ここで示した例は依然として非常に単純なタスクです。実際のタスクはこれらの例よりもはるかに複雑で、その過程では大小さまざまな多くの困難に直面することになります。この質問についてはさらに数行、その質問についてはさらに数行を書いてください。少し複雑なタスクでは、数百行のマルチレベルのネストされた SQL を記述することになるのも不思議ではありません。さらに、これらの数百行はステートメントであることが多く、エンジニアリング上の理由から、SQL はデバッグが難しく、複雑なクエリ分析の難しさをさらに悪化させています。
他の例
これらの側面を説明するために、さらにいくつかの例を挙げてみましょう。
この例の SQL をできるだけ単純にするために、ここでは多数のウィンドウ関数が使用されているため、ウィンドウ関数をサポートする ORACLE データベース構文が採用されています。通常、他の構文を使用してこれらの SQL を記述することはより複雑です。データベース。
これらの問題自体はそれほど複雑なものではなく、日常のデータ分析でもよく出てくる問題ですが、SQLではすでに難しい問題です。
収集障害
順序付き計算はバッチ データ計算で非常に一般的です (上位 3/3 位を取る、前回の問題と比較するなど)。ただし、SQL は数学における順序なしセットの概念を引き続き使用しており、順序付き計算を直接実行することはできません。アイデア方法を変更することによってのみ調整できます。
課題1 社内の中年層の社員
select name, birthday
from (select name, birthday, row_number() over (order by birthday) ranking
from employee )
where ranking=(select floor((count(*)+1)/2) from employee)
中央値は一般的な計算ですが、本来はソートされたセットの中央のメンバーを取り出すのは非常に簡単です。ただし、SQL の順序なしコレクションのメカニズムには、場所によってメンバーに直接アクセスするメカニズムが用意されていないため、人為的にシーケンス番号フィールドを作成し、条件付きクエリ メソッドを使用してそれを選択する必要があり、サブクエリを使用して完了することになります。
タスク 2 特定の銘柄の連続上昇は最長何営業日ですか
select max (consecutive_day)
from (select count(*) (consecutive_day
from (select sum(rise_mark) over(order by trade_date) days_no_gain
from (select trade_date,
case when
closing_price>lag(closing_price) over(order by trade_date)
then 0 else 1 END rise_mark
from stock_price) )
group by days_no_gain)
順序付けされていないコレクションは、アイデアの歪みにつながる可能性もあります。
従来の連続上昇日数の計算方法:連続上昇日を記録する一時変数に初期値0を設定し、前日と比較し、上昇していなければ0クリアする上昇した場合は 1 を追加します。サイクルの終了時に、値が最大値に表示されることを確認します。
この処理はSQLでは記述できないので、考え方を変えて、開始日から当日までの非上昇日数の累計を計算し、その最大数を求める必要があります。この SQL 文を読むのは簡単ではありませんが、書き出すのはさらに難しいです。
収集が完了していません
コレクションがバッチ データ計算の基礎であることは疑いの余地がありません。SQL にはセットの概念がありますが、単純な結果セットを記述することに限定されており、応用範囲を拡張するための基本的なデータ型としてセットを使用するわけではありません。
タスク 3 社内の他の従業員と同じ誕生日の従業員
select * from employee
where to_char (birthday, ‘MMDD’) in
( select to_char(birthday, 'MMDD') from employee
group by to_char(birthday, 'MMDD')
having count(*)>1 )
グループ化の本来の目的は、ソース コレクションを複数のサブコレクションに分割することであり、戻り値もこれらのサブセットである必要があります。ただし、SQL はこの「セットのセット」を表現できないため、これらのサブセットの集計計算の次のステップで通常の結果セットを形成する必要があります。
ただし、必要なのはサブセットの要約値ではなく、サブセット自体である場合があります。このとき、ソースコレクションからグループ化して得られた条件を用いて再度クエリを実行する必要があり、必然的にサブクエリが発生します。
タスク 4 各科目の上位 10 人の生徒を見つける
select name
from (select name
from (select name,
rank() over(partition by subject order by score DESC) ranking
from score_table)
where ranking<=10)
group by name
having count(*)=(select count(distinct subject) from score_table)
収集のアイデアでは、主題をグループ化した後、サブセットを並べ替えてフィルター処理して各主題の上位 10 件を選択し、これらのサブセットの交差を実行してタスクを完了します。ただし、SQLでは「集合の集合」を表現することができず、集合数が不定の集合の交差演算ができないため、この時は考え方を変えて、ウィンドウ関数を使って上位10位を見つける必要があります。科目ごとにグループ化し、出現回数が科目数と等しい生徒を見つけるため、理解が困難になります。
オブジェクト参照の欠如
SQLではデータテーブル間の参照関係は同じ値の外部キーによって保持されており、外部キーが指すレコードをそのままそのレコードの属性として利用することはできません。テーブルの結合やサブクエリを完了する必要があり、記述が面倒で非効率であるだけではありません。
課題5 女性管理職の男性社員
複数のテーブルと結合する
select A.*
from employee A, department B, employee C
where A.department=B.department and B.manager=C.name and
A.gender='male' and C.gender='female'
サブクエリあり
select * from employee
where gender='male' and department in
(select department from department
where manager in
(select name from employee where gender='female'))
従業員テーブルの部門フィールドが部門テーブルのレコードを指し、部門テーブルの管理者フィールドが従業員テーブルのレコードを指す場合、クエリ条件は次の直感的で効率的な形式で簡単に記述することができます。
where gender='male' and department.manager.gender='female'
しかし、SQL では、上記の 2 つの明らかにわかりにくいステートメントを作成するには、複数テーブルの結合またはサブクエリしか使用できません。
タスク 6 従業員の最初の就職先の会社
複数のテーブルと結合する
select name, company, first_company
from (select employee.name name, resume.company company,
row_number() over(partition by resume. name
order by resume.start_date) work_seq
from employee, resume where employee.name = resume.name)
where work_seq=1
サブクエリあり
select name,
(select company from resume
where name=A.name and
start date=(select min(start_date) from resume
where name=A.name)) first_company
from employee A
オブジェクト参照メカニズムや完全に集約された SQL はなく、サブテーブルをメイン テーブルの属性 (フィールド値) として扱うこともできません。サブテーブルのクエリでは、ステートメントの複雑さを増すために複数テーブルの結合を使用し、メイン テーブルのレコードと 1 対 1 で対応するように結果セットをフィルタリングまたはグループ化します (接続されたレコードはサブテーブルと 1 対 1 で対応します)。 -one); サブクエリを使用して、メイン テーブル レコードに関連するサブテーブル レコードのサブセットを毎回一時的に計算すると、全体の計算量が増加し (サブクエリには with 句を使用できません)、記述が複雑になります。
SPLの導入
問題は解決したので、次は解決策について話します。
実際、問題を分析する際には、解決策がある程度特定され、SQLのこうした難点を克服するために計算言語を再設計することで、問題が解決されます。
それが SPL が発明された理由です。
SPL はオープンソースのプログラミング言語で、正式名は Structured Process Language で、SQL からわずか 1 語離れています。目的は、構造化データの操作をより適切に解決することです。SPL は順序性を重視し、オブジェクト参照メカニズムをサポートすることで完全な収集を実現し、前述の「ソリューションの翻訳」の難易度を大幅に軽減します。
ここでのスペースは、SPL を詳しく紹介するのには適切ではないので、雰囲気を感じていただくために、前のセクションの例の SPL コードのみをリストします。
タスク1
あ | |
1 | =employee.sort(誕生日) |
2 | =A1((A1.len()+1)/2) |
ソートセットベースの SPL の場合、位置による値のフェッチは簡単なタスクです。
タスク2
あ | |
1 | =stock_price.sort(取引日) |
2 | =0 |
3 | =A1.max(A2=if(終値>終値[-1],A2+1,0)) |
SPL は自然な思考プロセスに従って計算コードを記述するだけです。
タスク3
あ | |
1 | =従業員.グループ(月(誕生日),日(誕生日)) |
2 | =A1.select(~.len()>1).conj() |
SPL は、グループ化された結果セットを保存し、通常のコレクションと同様に処理を続行できます。
タスク4
あ | |
1 | =スコアテーブル.グループ(件名) |
2 | =A1.(~.rank(スコア).pselect@a(~<=10)) |
3 | =A1.(~(A2(#)).(名前)).iset() |
SPL を使用するには、思考プロセスに従って計算コードを記述するだけです。
タスク5
あ | |
1 | =employee.select(gender=="男性" && 部門.マネージャー.gender=="女性") |
オブジェクト参照をサポートする SPL は、外部キーが独自のプロパティとして指すレコードのフィールドに簡単にアクセスできます。
タスク6
あ | |
1 | =employee.new(名前,履歴書.minp(開始日).会社:最初の会社) |
SPL は、他のフィールドにアクセスする場合と同様に、サブテーブル コレクションをメイン テーブル フィールドとしてサポートし、サブテーブルを再計算する必要はありません。
SPL には、便利なデバッグ機能を提供する直感的な IDE があり、コードをステップ実行してコード作成の複雑さをさらに軽減できます。
アプリケーション内計算のために、SPL は SQL などの Java アプリケーションに統合できる標準 JDBC ドライバーを提供します。
…
Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
Statement st = connection.();
CallableStatement st = conn.prepareCall("{call xxxx(?,?)}");
st.setObject(1, 3000);
st.setObject(2, 5000);
ResultSet result=st.execute();
...