2008-09-09 6 views
11

他のプログラマがSQLCommandオブジェクトのCommandTextとして実行するために動的SQL文字列をどのように生成しているのか尋ねたいと思います。SQLを動的に生成する標準的な方法はありますか?

私は、ユーザが生成したWHERE句とSELECTフィールドを含むパラメータ化されたクエリを生成しています。クエリが複雑な場合もあり、さまざまなパーツの作成方法について多くの制御が必要です。

現在、多くのループとswitch文を使用して、必要なSQLコードフラグメントを生成し、必要なSQLパラメータオブジェクトを作成しています。この方法は従うのが難しく、メンテナンスが本当の雑用になります。

これを行うクリーナー、より安定した方法がありますか?

任意の提案??

EDIT: は私の以前の記事に詳細を追加するには:

1.私は本当に私のクエリをテンプレート化することができないため、要件に。それはあまりにも多く変更されます。

  1. Count()のような集計関数を許可する必要があります。これはGroup By/Having句に影響します。また、ネストされたSELECT文も発生します。これは、次に使用される列名に影響します
  2. 一部の連絡先データはXML列に格納されます。ユーザーはこのデータをAS ASと他のリレーショナル列と一緒に照会できます。その結果、xml列はGroup By節[SQL構文]に現れません。
  3. Row_Number()SQL関数を使用する効率的なページング手法を使用しています。結果として、Tempテーブルを使用して、サブセットを選択する前に2番目のクエリを避けるために@@ rowcountを取得する必要があります。

私はいくつかのコード(ホラー!)を表示しますので、あなたは私が扱っているもののアイデアを持っています。この例:で

sqlCmd.CommandText = "DECLARE @t Table(ContactId int, ROWRANK int" + declare 
     + ")INSERT INTO @t(ContactId, ROWRANK" + insertFields + ")"//Insert as few cols a possible 
     + "Select ContactID, ROW_NUMBER() OVER (ORDER BY " + sortExpression + " " 
     + sortDirection + ") as ROWRANK" // generates a rowrank for each row 
     + outerFields 
     + " FROM (SELECT c.id AS ContactID" 
     + coreFields 
     + from   // sometimes different tables are required 
     + where + ") T " // user input goes here. 
     + groupBy+ " " 
     + havingClause //can be empty 
     + ";" 
     + "select @@rowcount as rCount;" // return 2 recordsets, avoids second query 
     + " SELECT " + fields + ",field1,field2" // join onto the other cols n the table 
     +" FROM @t t INNER JOIN contacts c on t.ContactID = c.id" 
     +" WHERE ROWRANK BETWEEN " + ((pageIndex * pageSize) + 1) + " AND " 
     + ((pageIndex + 1) * pageSize); // here I select the pages I want 



。私はXMLデータを照会しています。純粋なリレーショナルデータの場合、クエリはずっと簡単です。 セクション変数はそれぞれStringBuildersです。節はそのように構築されている場合:好奇心のうち

//Add Parameter to SQL Command 
AddParamToSQLCmd(sqlCmd, "@p" + z.ToString(), SqlDbType.VarChar, 50, ParameterDirection.Input, qc.FieldValue); 
// Create SQL code Fragment 
where.AppendFormat(" {0} {1} {2} @p{3}", qc.BooleanOperator, qc.FieldName, qc.ComparisonOperator, z); 
+0

実際にはオプションがないかもしれませんが、オラクルがこれらのSQL文を遭遇するたびにハード解析して処理速度を落とすようなデータベースを保持しているかもしれません。さらに悪いことに、キャッシュされた説明をスタックから外して、他のユーザーに同じように影響を与えます。 – ScottCher

答えて

2

私の最近のプロジェクトの1つでこれを行う必要がありました。ここで私はSQLを生成するために使用していますスキームは次のとおりです。

  • クエリーの各コンポーネントはオブジェクトによって表されている(私の場合にはDB内のテーブルにマップLINQのツーSQLのエンティティであります)。ですから、私は次のクラスを持っています:Query、SelectColumn、Join、WhereCondition、Sort、GroupBy。これらの各クラスには、クエリのそのコンポーネントに関するすべての詳細が含まれています。
  • 最後の5つのクラスはすべてQueryオブジェクトに関連しています。したがって、Queryオブジェクト自体には各クラスのコレクションがあります。
  • 各クラスには、それが表すクエリの部分のSQLを生成できるメソッドがあります。だから、全体的なクエリを作成すると、今度はサブコレクションのすべてを列挙し、それはまだ少し複雑ですが、最終的にあなた

それぞれGenerateQuery()メソッドを呼び出しQuery.GenerateQuery()を呼び出して終了しますクエリの個々の部分のSQL生成がどこから発生しているかを知っている(そして私は大きなswitch文はないと思う)。そして、StringBuilderの使用を忘れないでください。

0

、あなたのデータへのアクセスを管理するためのORMを使用して考えられています。あなたが実装しようとしている機能の多くはすでにそこにあるかもしれません。それは、車輪を再発明しないことが最善であるため、見るべきことかもしれません。

1

CodeSmithのようなコード生成ツールで使用されるアプローチを試すことができます。プレースホルダを使用してSQLテンプレートを作成します。実行時にテンプレートを文字列に読み込み、プレースホルダを実際の値で置き換えます。これは、すべてのSQLコードがパターンに従う場合にのみ役立ちます。

1

GulzarとRyan Lanciauxは、CodeSmithとORMについて言及しています。動的SQLを生成する際に、どちらを使用しても現在の負担を軽減または排除できます。パラメータ化されたSQLを使用する現在のアプローチは、SQLインジェクション攻撃に対して十分に保護されているため、賢明です。

コメントする実際のコードサンプルはありませんが、現在使用しているループとスイッチのステートメントに情報に基づく選択肢を提供することは難しいです。しかし、CommandTextプロパティを設定していると言われているので、実装でstring.Formatを使用することをお勧めします(まだ使用していない場合)。私はあなたのコードを再構成しやすくすることで、読みやすさと理解を向上させると思います。

1

通常はこのようなものです:私もちょうどオームズで出始めるよ

string query= "SELECT {0} FROM .... WHERE {1}" 
StringBuilder selectclause = new StringBuilder(); 
StringBuilder wherecaluse = new StringBuilder(); 

// .... the logic here will vary greatly depending on what your system looks like 

MySqlcommand.CommandText = String.Format(query, selectclause.ToString(), whereclause.ToString()); 

。そのうちの1つを見てみたいかもしれません。 ActiveRecord/Hibernateはグーグルにとって良いキーワードです。

0

ORMはすでに動的SQL生成の問題を解決しました(私はNHibernate/ActiveRecordを好む)。これらのツールを使用すると、ユーザー入力をループしてExpressionオブジェクトの配列を生成することで、未知数の条件でクエリを作成できます。次に、そのカスタム式セットで組み込みのクエリメソッドを実行します。

List<Expression> expressions = new List<Expression>(userConditions.Count); 
foreach(Condition c in userConditions) 
{ 
    expressions.Add(Expression.Eq(c.Field, c.Value)); 
} 
SomeTable[] records = SomeTable.Find(expressions); 

より「表現」のオプションがあります: - nullでない/ nullに、より非平等、大きい/小さい、などは、私はちょうど作っ「条件」タイプ、あなたはおそらくにあなたのユーザー入力を詰め込むことができます有用なクラス。

1

これをコードから実際に行う必要がある場合は、ORMがおそらくそれをきれいに保つための方法です。

しかし、私は、うまく機能し、動的クエリに伴うパフォーマンス上の問題を避けるために、新しいクエリプランを作成する必要があるSQLの変更や、インデックスのさまざまな要求を避けることができます。

が可能なすべてのパラメータを受け取り、ストアドプロシージャを作成し、where句には、このようなものを使用します。コードから、

where... 
and (@MyParam5 is null or @MyParam5 = Col5) 

そして、それはDBNull.Valueをするときにパラメータ値を設定することがはるかに簡単です生成するSQL文字列を変更するのではなく、適用できません。

DBAはクエリチューニングのための場所が1つあり、SQLは読みやすく、さまざまなクエリを見つけるためにプロファイラトレースを掘り下げる必要がないため、非常に幸せですあなたのコードによって生成されます。

2

ブラックボックス動的クエリビルダーのような独自のFilterCriteriaオブジェクトを作成しました。 SelectClause、WhereClause、GroupByClause、OrderByClauseのコレクションプロパティを持ちます。また、CommandText、CommandType、およびMaximumRecordsのプロパティも含まれています。

FilterCriteriaオブジェクトをデータロジックに渡し、データベースサーバーに対して実行し、パラメータ値を動的コードを実行するストアドプロシージャに渡します。

私たちのためにうまくいきます。SQL生成をオブジェクトにうまく収めています。

関連する問題