web-dev-qa-db-ja.com

SQLがリファクタリングできないのはなぜですか?

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

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. 他の何か

39
ebrts

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

私の雇用主は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

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

これは次のようにして行うことができます:

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

しかし、それはコードをよりクリーンで、より読みやすく、より乾燥させるための長い道のりです。

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

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

あなたの不満の一部は「なぜCTEについて知らないのですか?」または「なぜ私のDBはCTEをサポートしないのですか?」

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

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

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

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

25
Meower68

データベースビューの作成を制限することは、多くの場合、データベースのパフォーマンスの問題に執着している組織によって行われます。これは、SQLの技術的な問題ではなく、組織文化の問題です。

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

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

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

36
Greg Burghardt

あなたの質問/観点からあなたが見逃しているかもしれないと私が思うのは、SQLが(セット演算などを使用して)セットに対して演算を実行するということです。

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

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

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

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

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

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

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

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

12
Toni Kostelac

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

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

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

データベースが正規化されるほど、結合が多くなり、データベースが大きなExcelシートのようになり、副選択が多くなります。

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

それでは、なぜ非常に多くのモノリシックな非リファクタリングクエリが表示されるのですか?

  • SQLは、多くの点でプログラミング言語ではありません。
  • 悪いデータベース設計。
  • SQLにあまり堪能でない人々。
  • データベースに対する権限がない(たとえば、ビューの使用が許可されていない)
  • リファクタリングのさまざまな目標。

(私にとって、SQLで経験を積むほど、クエリのサイズは小さくなります。SQLには、あらゆるスキルレベルの人々が何をしていても仕事を終わらせる方法があります。)

6
Pieter B

職務の分離

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

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

これらすべてが、DBAが、特定のアプリケーションのコードのためだけのビューを追加したくない理由を説明しています。

SQLデザイン

ニースの複雑なクエリの1つを分解すると、サブクエリが別のサブクエリに依存するパラメーターを必要とすることがよくあります。

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

残念ながら、そうすることで、調整されたクエリよりも多くのデータにアクセスする必要が生じ、効果が低下することがあります。

独自の拡張

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

しかし、結局のところ問題は何ですか?

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

したがって、リファクタリングを成功させるために:

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

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

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

2
Christophe

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

DBA(および私はDBAと開発者の両方である人物として話しています)は、世界をかなりバイナリの方法で表示する傾向があるため、パフォーマンスの低下を認識しているため、ビューや関数などに反対することがよくあります。

最後に、複雑な結合の必要性は、 [〜#〜] nf [〜#〜] の観点からは最適ではないにもかかわらず、非正規化されたテーブルは高いパフォーマンスを発揮するという認識により減少しました。

ポイント2で発生する [〜#〜] linq [〜#〜] のようなテクノロジーを使用してクライアント側でクエリを実行する傾向もあります。

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

それは、DBA /アーキテクト/技術リーダーがこの点に関してどの程度遠くから出ていくかによって、実際に決まると思います。多数の結合を持つVanilla SQL以外は何も許可しない場合、巨大なクエリが発生する可能性があります。これで行き詰まっている場合は、レンガの壁に頭をぶつけないで、エスカレーションしてください。一般に、少しの妥協で物事を行うより良い方法があります-特にあなたが利点を証明することができるならば。

1
Robbie Dee