SQLクエリをテストする最良の方法[終了]


109

複雑なSQLクエリでエラーが発生し続けるという問題に遭遇しました。本質的に、これは誤った顧客やそのような他の「問題」にメールを送信することになります。

そのようなSQLクエリを作成することで誰もが経験したことは何ですか?隔週で新しいデータのコホートを作成しています。

だからここに私の考えのいくつかとそれらへの制限があります:

  • テストデータの作成これにより、すべての正しいデータがあることが証明されますが、本番環境での異常の排除は強制されません。これは、今日は間違っていると考えられるデータですが、10年前は正しかった可能性があります。それは文書化されていなかったため、データが抽出された後にのみそれについて知っています。

  • ベン図とデータマップを作成するこれはクエリのデザインをテストするための確かな方法のようですが、実装が正しいことを保証するものではありません。これにより、開発者は前もって計画を立て、執筆中に何が起こっているのかを考えることができます。

あなたが私の問題に与えることができる任意の入力をありがとう。

回答:


164

200行の関数を持つアプリケーションを作成することはありません。これらの長い関数は、それぞれが明確に定義された単一の責任を持つ小さな関数に分解します。

なぜそのようなSQLを書くのですか?

関数を分解するの同じように、クエリを分解します。これにより、より短く、簡単に、理解しやすく、テストしやすくなり、リファクタリングしやすくなります。また、手続き型コードの場合と同じように、それらの間に「シム」とそれらの周りの「ラッパー」を追加できます。

これどうやってやるの?それぞれの重要なことを行うことにより、クエリはビューになります。その後、作曲、あなたがより原始的な機能のうち、より複雑な機能を構成するのと同様に、これらの単純なビューのうち、より複雑なクエリを。

そして、すばらしいのは、ほとんどのビューの構成で、RDBMSからまったく同じパフォーマンスが得られることです。(あなたはしませんいくつかのために;?だから何時期尚早の最適化は諸悪の根源であるコードを正しくまず、。その後、最適化あなたがする必要がある場合。)

複数のビューを使用して複雑なクエリを分解する例を次に示します。

この例では、各ビューが変換を1つだけ追加するため、それぞれを独立してテストしてエラーを見つけることができ、テストは簡単です。

この例のベーステーブルは次のとおりです。

create table month_value( 
    eid int not null, month int, year int,  value int );

このテーブルには、月と年の2つの列を使用して1つのデータム、つまり絶対月を表すため、欠陥があります。新しい計算列の仕様は次のとおりです。

これを線形変換として実行し、(年、月)と同じように並べ替え、(年、月)のタプルには値が1つだけあり、すべての値が連続するようにします。

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

これで、テストする必要があるのは仕様に固有です。つまり、タプル(年、月)は1つだけ(absolute_month)あり、(absolute_month)は連続しています。テストを書いてみましょう。

テストはselect、次の構造を持つSQL クエリになります。テスト名とcaseステートメントが連結されています。テスト名は単なる任意の文字列です。case文は単なるcase whenテスト文then 'passed' else 'failed' endです。

テスト文は、テストが合格するために真でなければならないSQL選択(サブクエリ)です。

これが最初のテストです。

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

そのクエリを実行すると、次の結果が生成されます。 For every (year, month) there is one and only one (absolute_month): passed

month_valueに十分なテストデータがある限り、このテストは機能します。

十分なテストデータのテストも追加できます。

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

次に、連続しているかどうかをテストします。

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

次に、クエリであるテストをファイルに入れ、そのスクリプトをデータベースに対して実行します。実際、データベースに対して実行するスクリプト(またはスクリプト、関連するビューごとに1つのファイルを推奨)にビュー定義を保存する場合、各ビューのテストを同じスクリプトに追加して、(re -)ビューを作成すると、ビューのテストも実行されます。そうすることで、ビューを再作成するときに回帰テストを受け、ビューの作成が本番環境に対して実行されると、ビューも本番環境でテストされます。


27
これは、SQLでクリーンなコードと単体テストを見るのが初めてで、その日は幸せです:)
Maxime ARNSTAMM

1
素晴らしいSQLハック
CodeFarmer 2017

13
これはすばらしいことですが、なぜ列に1文字の名前を使用し、ほとんど判読できないビュー名を使用するのでしょうか。なぜSQLはPythonよりも自己文書化または読みにくいのですか?
snl 2017年

1
SQL / DBの世界で私が見たことのない便利なものに対する素晴らしい説明。また、ここでデータベースをテストした方法も気に入っています。
Jackstine 2017

警告と同じように、SQLビューで結合するSQLビューは、PostgreSQLでは非常にうまく機能しません。ただし、この手法をM $ SQLで効果的に使用しました。
Ben Liyanage

6

何度でもリロードできるテストシステムデータベースを作成します。データをロードするか、データを作成して保存します。それをリロードする簡単な方法を作成します。開発システムをそのデータベースに接続し、本番環境に進む前にコードを検証します。問題が本番環境に入るように管理するたびに自分を蹴ります。テストスイートを作成して、既知の問題を検証し、テストスイートを徐々に拡大します。


4

DbUnitを確認したい場合があるので、固定データセットを使用してプログラムの単体テストを作成してみてください。そうすれば、予測可能な結果が多少あるクエリを作成できるはずです。

SQL Serverの実行スタックをプロファイリングして、すべてのクエリが本当に正しいものかどうかを確認することもできます。たとえば、正しい結果と正しくない結果の両方を返すクエリを1つだけ使用していて、クエリが明確になっている場合などです。使用されているのは問題ですが、アプリケーションがコードの異なるポイントで異なるクエリを送信している場合はどうでしょうか?

クエリを修正しようとしても無駄です...とにかく、不正なクエリが間違った結果を引き起こしている可能性があります。


2

Re:tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

これは、連続する月のam値が連続することのみをチェックすることに注意してください。連続するデータが存在することはありません(おそらく最初に意図したものです)。amの計算が完全にオフであっても、ソースデータが連続していない場合(たとえば、月が偶数である場合)は常に成功します。

また、何か不足していますか、またはそのON句の後半が間違った月の値にぶつかっていますか?(つまり、2011年12月が1/2010の後に来ることを確認します)

さらに悪いことに、私が正しく覚えていれば、SQL Serverでは、オプティマイザが仮想ハンドを空中に投げてすべてのリクエストで全テーブルスキャンを開始する前に、少なくとも10レベル未満のビューを許可しているため、このアプローチをやりすぎないでください。

あなたのテストケースから一体をテストすることを忘れないでください!

それ以外の場合は、SqlUnitまたはDbUnitまたはその他の* Unitを使用してデータの予想される結果を自動的にチェックし、必要に応じてデータを確認、維持、更新することで、入力のほとんどまたはすべての形式を網羅する非常に幅広いデータセットを作成します。行く方法。

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