SQLのリファクタリングが容易ではないのはなぜですか?[閉まっている]


39

新しい開発者が長い関数を書くことは誰もが知っています。進歩するにつれて、コードを小さな断片に分割するのが上手になり、経験がそうすることの価値を教えてくれます。

SQLを入力します。はい、SQLのコードについての考え方は、手順についてのコードについての考え方とは異なりますが、この原則は同じように思えます。

次の形式のクエリがあるとします。

select * from subQuery1 inner join subQuerry2 left join subquerry3 left join join subQuery4 

いくつかのIDや日付などを使用する

これらのサブクエリはそれ自体が複雑であり、独自のサブクエリを含む場合があります。他のプログラミングコンテキストでは、複雑なサブクエリ1〜4のロジックは、それらすべてを結合する親クエリに一致するとは考えられません。私が手続き型コードを書いている場合にそれらが関数であるように、それらのサブクエリはビューとして定義されるべきであるように非常に簡単です。

では、なぜ一般的な慣行ではないのでしょうか?なぜこれらの長いモノリシックSQLクエリを頻繁に書くのですか?なぜ手続き型プログラミングが広範な関数の使用を奨励するように、SQLが広範なビューの使用を奨励しないのか。(多くのエンタープライズ環境では、ビューの作成は簡単にできるものではありません。要求と承認が必要です。他のタイプのプログラマーが関数を作成するたびに要求を送信しなければならないと想像してください!)

私は3つの可能な答えを考えました:

  1. これはすでに一般的であり、私は経験の浅い人々と仕事をしています

  2. 経験豊富なプログラマーは、手続き型コードを使用してハードデータ処理の問題を解決することを好むため、複雑なSQLを作成しません

  3. 他の何か


12
ビューを使用してデータベースにクエリを実行し、ストアドプロシージャを使用してデータベースを変更するだけの組織があります。
ピーターB

3
SQLは、通常の手続き型コードほどDRYになることはないということをようやく受け入れたとき、私にとってはもっと楽しくなりました。
グラハム

1
4. SQLは非常に古く、何十年も実質的に更新されていません。非常に複雑なものについては、多くのチームがストアドプロシージャを選択します。そのために異なる句を追加できます。一時テーブルにデータをステージングするためにジョブを実行してから、それに参加する必要がある場合があります。宣言型言語と手続き型言語の違いを見てください。
ベリンロリッチ

8
また、1つの理由は、ビューを使用するときに発生する可能性がある「三角結合」と呼ばれる恐ろしいパフォーマンスの問題があることです(もちろん偶然です)。クエリがビューAとビューBを結合しているが、その実装でビューAがビューBを再利用している場合、その問題が見え始めます。そのため、人々はしばしば、ビューへのリファクタリングの観点から実際に何が最も効果的かを確認できる単一のモノリシッククエリを作成することから始め、その後、デッドラインがヒットし、モノリスがプロダクションに移行します。すべてのソフトウェア開発者の98%のようなもの、本当に:) :)
Stephen Byrne

3
「他のタイプのプログラマーが関数を作成するたびにリクエストを送信しなければならないと想像してください」...うーん。コードレビューをしませんか?
svidgen

回答:


25

主な問題は、すべてのデータベースが共通テーブル式をサポートしているわけではないことです。

私の雇用主はDB / 2を非常に多くのことに使用しています。最新バージョンではCTEがサポートされているため、次のようなことができます。

with custs as (
    select acct# as accountNumber, cfname as firstName, clname as lastName,
    from wrdCsts
    where -- various criteria
)
, accounts as (
    select acct# as accountNumber, crBal as currentBalance
    from crzyAcctTbl
)
select firstName, lastName, currentBalance
from custs
inner join accounts on custs.accountNumber = accounts.accountNumber

その結果、テーブル/フィールド名を大幅に短縮することができ、より読みやすい名前の一時ビューを本質的に作成し、それを使用できます。確かに、クエリは長くなります。しかし、その結果、かなり明確に分離されたもの(CTEを使用して関数を使用してDRYを取得する)を記述し、非常に読みやすいコードを作成できます。また、サブクエリを分割して、あるサブクエリが別のサブクエリを参照できるため、すべてが「インライン」ではありません。ときどき、1つのCTEを作成し、それから他の4つのCTEがすべてそれを参照し、最後の4つの結果をメインクエリに結合させました。

これは次の方法で実行できます。

  • DB / 2
  • PostGreSQL
  • オラクル
  • MS SQLサーバー
  • MySQL(最新バージョン、まだ少し新しい)
  • おそらく他の人

しかし、コードをよりクリーンで読みやすく、よりドライにするための長い道のりを歩んでいます。

私は、さまざまなクエリにプラグインできるCTEの「標準ライブラリ」を開発しました。これにより、新しいクエリをすぐに始めることができます。それらのいくつかは、私の組織内の他の開発者にも受け入れられ始めています。

そのうち、これらの一部をビューに変換して、この「標準ライブラリ」をコピー/貼り付けする必要なく使用できるようにすることは理にかなっています。しかし、私のCTEは、1つのCTEをmodなしで非常に広く使用することができなかったため、ビューを作成する価値があるかもしれないさまざまなニーズのために、微調整されています。

あなたの不満の一部は「なぜ私がCTEについて知らないのか」と思われるでしょう。または「DBがCTEをサポートしないのはなぜですか?」

更新については...そうです、CTEを使用できますが、私の経験では、set句内およびwhere句内で使用する必要があります。updateステートメント全体の前に1つ以上を定義し、set / where句に「メインクエリ」の部分を含めることができれば便利ですが、それは機能しません。また、更新しているテーブルのあいまいなテーブル/フィールド名を避けることはできません。

削除にCTEを使用できます。そのテーブルから削除するレコードのPK / FK値を決定するには、複数のCTEが必要になる場合があります。繰り返しますが、変更するテーブルのテーブル/フィールド名が不明瞭になることは避けられません。

挿入に対して選択を行うことができるので、挿入にCTEを使用できます。いつものように、変更しているテーブルのあいまいなテーブル/フィールド名を扱っているかもしれません。

SQLでは、getter / setterを使用して、テーブルをラップするドメインオブジェクトに相当するものを作成できません。そのためには、何らかの手続き型のORMと、より手続き的な/ OOプログラミング言語を使用する必要があります。この性質のものをJava / Hibernateで記述しました。


4
私たちは、Big CTE氏を最悪のSQLを書く人にしてもらいました。問題は、CTEのが悪い抽象化の選択肢だったとオプティマイザは、あなたがに入れておきboneheadedアルゴリズムを元に戻すことはできませんでした。
ジョシュア

3
また、ORMはパフォーマンス面でもかなり厄介なことを実行できます。特に、ゲッターとセッターを使用して大量のデータをフェッチする場合は特にそうです。Hibernateは、1つの大きな結合クエリではなく、数百の個別クエリを使用することで有名です。これは、各クエリにオーバーヘッドがある場合の問題です。
user3067860

2
@Joshuaどの言語でも悪いコードを書くことができます。SQLを含む。しかし、適切に行われたCTEへのリファクタリングは、人間が解析しやすいボトムアップ設計を作成できます。私はどの言語を扱っているかに関係なく、それを望ましい特性と見なす傾向があります:-)
Meower68

2
他の答えは素晴らしいですが、これは私が個人的に探していたものです。「なぜCTEについて知らないのか」が私の問題の大部分でした。
ebrts

2
@ Meower68 CTEを広範に使用すると、人々が適切に結合を学習し、適切なデータベース設計を学習できなくなるリスクはありませんか?私はCTEの価値を支持しますが、それはまた、あなたがすべきではないサブクエリを扱うことをあまりにも簡単にします。
ピーターB

36

データベースビューの作成のロックダウンは、多くの場合、データベースのパフォーマンスの問題に偏執的な組織によって行われます。これは、SQLの技術的な問題ではなく、組織文化の問題です。

それ以上に、大規模なモノリシックSQLクエリは何度も記述されます。これは、ユースケースが非常に限定されているため、他のクエリでSQLコードをほとんど再利用できないためです。複雑なクエリが必要な場合、通常は非常に異なるユースケース用です。多くの場合、別のクエリからSQLをコピーすることは出発点ですが、他のサブクエリと新しいクエリのJOINにより、コピーしたSQLを変更して、別の言語の「関数」のような抽象化を壊すだけです。に使用されます。SQLをリファクタリングするのが難しい最も重要な理由に至ります。

SQLは、具体的なデータ構造のみを扱い、抽象的な動作(または、あらゆる意味での抽象化)は扱いません。SQLは具体的なアイデアに基づいて記述されているため、再利用可能なモジュールに抽象化するものは何もありません。データベースビューはこれに役立ちますが、別の言語の「関数」と同じレベルではありません。データベースビューは、クエリであるため抽象化ではありません。実際、データベースビュークエリです。基本的にはテーブルのように使用されますが、サブクエリのように実行されるため、抽象的なものではなく具体的​​なものを扱っています。

抽象化により実装の詳細がその抽象化のコンシューマから隠されるため、コードがリファクタリングしやすくなります。ストレートSQLはそのような分離を提供しませんが、OracleのPL / SQLやSQL ServerのTransact-SQLのようなSQLの手続き的な拡張は、行を少し曖昧にし始めます。


「SQLは具体的なデータ構造のみを処理し、抽象的な動作(または単語の意味での抽象化)は処理しません。」私の観点からすると、SQLは完全に抽象的な振る舞いを扱い、言葉の意味での具体的なプログラミングではないため、これは奇妙なステートメントです。単純な単語「JOIN」に抽象化された複雑さのすべてを考慮してください。2つの異なるデータセットからマージされた結果を取得し、DBMSに任せて具体的なテクニックを決定し、対処しますインデックス作成、テーブルとサブクエリの違いの処理など...
Mason Wheeler

5
@MasonWheeler:私は、言語機能の実装ではなく、それが機能するデータの観点からSQLを考えていたと思います。データベース内のテーブルは抽象化のようには見えません。「phone_numbers」という表に電話番号が含まれているため、これらは具体的です。電話番号は抽象的な概念ではありません。
グレッグブルクハルト

12

あなたがあなたの質問/視点から欠落しているかもしれないと思うことは、SQLがセットで操作を実行することです(セット操作などを使用して)。

そのレベルで操作するとき、当然、エンジンに対する特定の制御を放棄します。カーソルを使用して手続き型のコードを強制することもできますが、経験から99/100回示されているように、そうするべきではありません。

SQLのリファクタリングは可能ですが、アプリケーションレベルのコードで使用されているのと同じコードリファクタリングの原則を使用していません。代わりに、SQLエンジン自体の使用方法を最適化します。

これはさまざまな方法で実行できます。Microsoft SQL Serverを使用している場合、SSMSを使用しておおよその実行計画を提供し、それを使用してコードを調整するために実行できる手順を確認できます。

@ greg-burghardtが述べたように、コードを小さなモジュールに分割する場合、SQLは通常、目的に応じて作成されたコードの一部であり、その結果です。それはあなたがそれをするために必要な一つのことだけを行い、他には何もしません。これはSOLIDのSに準拠しており、変更/影響を受けるのは1つの理由のみであり、そのクエリが何か他のことをする必要がある場合です。残りの頭字語(OLID)はここでは適用されません(SQLには依存関係の注入、インターフェイス、依存関係はありません)。使用しているSQLのフレーバーによっては、特定のクエリをラップして拡張できる場合がありますストアドプロシージャ/テーブル関数で、またはそれらをサブクエリとして使用するので、ある意味では、オープンクローズの原則がまだ適用されると思います。しかし、私は脱線します。

SQLコードの表示方法に関して、パラダイムをシフトする必要があると思います。その性質上、アプリケーションレベルの言語で使用できる多くの機能(ジェネリックなど)を提供できません。SQLはそのようなものになるように設計されたことはありません。データのセットを照会する言語であり、各セットは独自の方法で一意です。

そうは言っても、組織内で読みやすさが優先される場合、コードをより見栄えよくする方法があります。頻繁に使用されるSQLブロック(使用する一般的なデータセット)の一部をストアドプロシージャ/テーブル値関数に格納し、それらをクエリして一時テーブル/テーブル変数に格納した後、それらを使用してピースを1つの大規模なトランザクションに結合しますそうでなければ書くことはオプションです。私見では、SQLでそのようなことをする価値はありません。

言語として、それは誰でも、非プログラマーでも簡単に読めるように設計されています。そのため、非常に賢いことをしているのでなければ、SQLコードを小さなバイトサイズの断片にリファクタリングする必要はありません。私は個人的に、データウェアハウスETL /レポートソリューションに取り組んでいる間に大規模なSQLクエリを作成しましたが、何が起こっているのかという点ではまだすべてが明確でした。他の人には少し奇妙に見えるかもしれないものはすべて、簡単な説明を提供するためにそれと一緒に短いコメントのセットを取得します。

これがお役に立てば幸いです。


6

あなたの例の「サブクエリ」に焦点を当てます。

なぜ頻繁に使用されるのですか?彼らは人の自然な考え方を使用しているからです。私はこのデータのセットを持っているので、そのサブセットに対してアクションを実行し、それを他のデータのサブセットと結合します。サブクエリが表示される10回のうち9回は、間違って使用されています。サブクエリについての私の冗談は、結合を恐れる人々がサブクエリを使用することです。

このようなサブクエリが表示される場合、多くの場合、最適でないデータベース設計の兆候でもあります。

データベースが正規化されるほど、結合が増えるほど、データベースが大きなExcelシートのように見え、サブセレクトが増えます。

SQLのリファクタリングには、多くの場合、別の目標があります。パフォーマンスの向上、クエリ時間の改善、「テーブルスキャンの回避」です。それらはコードを読みにくくするかもしれませんが、非常に価値があります。

それでは、なぜリファクタリングされていない巨大なモノリシッククエリがたくさんあるのでしょうか?

  • SQLは、多くの点でプログラミング言語ではありません。
  • 悪いデータベース設計。
  • SQLにあまり精通していない人。
  • データベースを介したパワーオンなし(たとえば、ビューの使用を許可されていない)
  • リファクタリングのさまざまな目標。

(私にとって、SQLの経験が多いほど、クエリの規模は小さくなります。SQLには、すべてのスキルレベルの人々が何でも問題なく仕事を行えるようにする方法があります。)


6
「サブクエリ」は、正規化されていないデータベースのアドホック正規化であるのと同様に、適切に正規化されたデータベースの集約である可能性が高い
Caleth

@Calethそれは本当です。
ピーターB

5
適切に正規化されたデータベースであっても、テーブルと直接結合するのではなく、サブクエリと結合する必要があることがよくあります。たとえば、グループ化されたデータと結合する必要がある場合。
バーマー

1
@Barmar間違いなく、したがって10分の9のコメントです。サブクエリには場所がありますが、経験の浅い人々が使いすぎているのがわかります。
ピーターB

データベースの正規化(またはその欠如)の指標としての「サブクエリの数」のメトリックが好きです。
ジェイソン

2

職務分離

SQLの精神では、データベースは会社のデータを含む共有資産であり、それを保護することは非常に重要です。神殿の守護者としてDBAに入ります。

データベースに新しいビューを作成すると、永続的な目的に役立ち、ユーザーのコミュニティで共有されると理解されています。DBAビューでは、これは、ビューがデータの構造によって正当化されている場合にのみ受け入れられます。ビューのすべての変更は、アプリケーションを使用していないがビューを発見したユーザーを含め、現在のすべてのユーザーのリスクに関連付けられます。最後に、新しいオブジェクトの作成には、権限の管理が必要であり、ビューの場合は、基礎となるテーブルの権限と一貫性があります。

これらすべてが、DBAが個々のアプリケーションのコード専用のビューを追加することを好まない理由を説明しています。

SQLデザイン

素敵な複雑なクエリの1つを分解すると、サブクエリには別のサブクエリに依存するパラメータが必要になることが多いことがわかります。

したがって、ビューでのサブクエリの変換は、必ずしも説明したほど単純ではありません。変数パラメーターを分離し、パラメーターをビューの選択基準として追加できるようにビューを設計する必要があります。

残念ながら、そうすることで、より多くのデータにアクセスしなければならず、調整されたクエリよりも効率的でない場合があります。

独自の拡張機能

PL / SQLやT-SQLなどのSQLの手続き型拡張機能に一部の責任を移すことで、リファクタリングを期待できます。ただし、これらはベンダーに依存しており、技術的な依存関係が生じます。さらに、これらの拡張機能はデータベースサーバーで実行されるため、アプリケーションサーバーよりもスケーリングがはるかに難しいリソースの処理負荷が大きくなります。

しかし、最終的に問題は何ですか?

最後に、職務分離とその長所と限界を備えたSQL設計は本当の問題ですか?最終的に、これらのデータベースは、ミッションクリティカルな環境を含む非常に重要なデータを正常かつ確実に処理することが証明されました。

リファクタリングを成功させるために:

  • より良いコミュニケーションを検討してください。DBAの制約を理解してください。新しいビューがデータ構造によって正当化されていること、それが使い捨ての回避策ではなく、セキュリティに影響がないことをDBAに証明する場合、彼/彼女はそれを作成できることに確かに同意します。なぜなら、それは関心を共有するからです。

  • 最初に自分の家を掃除する:多くの場所で多くのSQLを生成することを強制するものは何もありません。アプリケーションコードをリファクタリングして、SQLアクセスを分離し、頻繁に使用される場合は再利用可能なサブクエリを提供するクラスまたは関数を作成します。

  • 改善チーム意識を:アプリケーションがDBMSエンジンによって、より効率的に行うことができ、タスクを実行していないことを確認してください。あなたが正しく指摘したように、手続き型アプローチとデータ指向型アプローチは、チームの異なるメンバーによって等しくマスターされていません。背景に依存します。ただし、システム全体を最適化するには、チーム全体でシステムを理解する必要があります。そのため、経験の浅いプレイヤーが車輪を再発明してDBの考えを経験のあるメンバーと共有しないように意識を高めてください。


+1ここでいくつかの素晴らしい点。SQLがどれほど悪いかを考えると、ビューを許可するDBAの無口さは、完全に理解できることがよくあります。また、SQLは、リソースが空いている場合や頻繁に実行される場合、ピアレビューから確実に利益を得ることができます。
ロビーディー

1

ポイント1と3について:ビューは唯一の方法ではありません。また、RDBMSに応じて、一時テーブル、マート、テーブル変数、集計列、CTE、関数、ストアドプロシージャ、および場合によってはその他の構成要素もあります。

DBA(および私はDBAと開発者の両方として働いている人として)は、かなりバイナリ形式で世界を見る傾向があるため、パフォーマンスのペナルティが原因で、ビューや機能などに反対することがよくあります。

後に、NFの観点からは最適化されていないにもかかわらず非正規化されたテーブルのパフォーマンスが高いという認識により、複雑な結合の必要性が減少しました。

また、ポイント2で取り上げたLINQなどのテクノロジーを使用して、クライアント側でクエリを実行する傾向もあります。

SQLはモジュール化するのが難しいことには同意しますが、クライアント側のコードとSQ​​Lの間には常に二分法がありますが、4GLは多少線を曖昧にしていますが、大きな進歩を遂げました。

これは、DBA /アーキテクト/テクノロジーリードがこの点でどれだけ譲歩するかによります。結合が多いバニラSQL以外を許可しない場合、巨大なクエリが発生する可能性があります。これにこだわっている場合は、レンガの壁に頭をぶつけないで、エスカレーションしてください。一般的に、少しの妥協点で物事を行うより良い方法があります-特に利点を証明できる場合。


1
「マート」コンストラクトについて聞いたことがありません。それは何ですか?
ビショップ

1
マートは、リポジトリ(マスターデータベース)のサブセットにすぎません。実行する必要のある特定の複雑なクエリがある場合、それらの要求を処理するために特別なデータベースを作成できます。非常に一般的な例は、レポートマートです。
ロビーディー

1
これがなぜ投票されたのか混乱した。質問に直接答えるわけではありませんが、「オプション3:これを処理する方法はたくさんあり、広く使われています」というかなり明確な暗黙の答えを与えます。
デウィモーガン

データマートに関するTIL。+1を持っている!
ビショップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.