3

何百ものストアド プロシージャがあり、その多くが動的 SQL を使用する大規模なアプリケーションを継承しました。扱っている SQL の種類をより適切に処理するには、これらすべてのストアド プロシージャのクエリ テキストを解析し、含まれている動的 SQL の完全な式を抽出する方法があれば非常に便利です。

単純化された式は次のようになります。

declare @query nvarchar(max)
set @query = 'SELECT col1,col2,col3 from ' + @DatabaseName + '.dbo.' + @TableName + ' WHERE {some criteria expression that also contains inline quotes}'

上記に対して求めている出力 (最終的には、すべてのストアド プロシージャを解析する単一のクエリで呼び出される) は次のとおりです。

SELECT col1, col2, col3 
FROM ' + @DatabaseName + '.dbo.' + @TableName + ' 
WHERE {some criteria expression that also contains inline quotes}

したがって、パラメーター値が渡された後の式ではなく、パラメーター名を含むストアド プロシージャ テキストのような式テキストです。

動的 SQL パラメータ名が であるというまったく安全ではない仮定で問題ない@queryので、SQL 式内でこれを検索して、テキストを抽出する開始位置として使用することは許容できますが、インラインには一重引用符があるためです。 、変数への割り当てがどこで完了したかを知る簡単な方法がありません。

この質問に [antlr] タグと [parsing] タグを含めているのは、これが T-SQL の能力を超えていると感じているからです。

PS: はい、「これを行うべきではない」ことは十分承知しています。

編集

以下の提案から、次のクエリを試しましたが、このコンテキストではあまり役に立ちませんでした:

SELECT 
db_name(dbid) DB_NAME
,cacheobjtype, objtype, object_name(objectid) ObjectName
,objectid 
,x.text
,usecounts 
--  , x.*,z.* ,db_name(dbid)
FROM 
sys.dm_exec_cached_plans z
CROSS APPLY sys.dm_exec_sql_text(plan_handle)  x
WHERE 
    --usecounts > 1 
    --objType = 'Proc' and  -- include if you only want to see stored procedures 
    db_name(dbid) not like 'ReportServer%' and db_name(dbid) <> 'msdb' and db_name(dbid) not like 'DBADB%' and db_name(dbid) <> 'master'
--ORDER BY usecounts DESC
ORDER BY objtype
4

1 に答える 1

5

最初の概算として、C# で を使用してそれを行う方法を次に示しますScriptDom

すべてのストアド プロシージャ定義のリストを取得するのは簡単です。これは、次の場合でも T-SQL で実行できます。

sp_msforeachdb 'select definition from [?].sys.sql_modules'

または、通常の方法でデータベースをスクリプト化するか、SMO を使用します。いずれにせよ、List<string>コードで消費するために、これらを何らかの形で取得できると思います。

Microsoft.SqlServer.TransactSql.ScriptDomNuGetパッケージとして利用できるので、それを新しいアプリケーションに追加します。問題の核心は、T-SQL スクリプトから対象のノードを取り出すビジターを作成することです。

class DynamicQueryFinder : TSqlFragmentVisitor {
  public List<ScalarExpression> QueryAssignments { get; } = new List<ScalarExpression>();
  public string ProcedureName { get; private set; }

  // Grab "CREATE PROCEDURE ..." nodes
  public override void Visit(CreateProcedureStatement node) {
    ProcedureName = node.ProcedureReference.Name.BaseIdentifier.Value;
  }

  // Grab "SELECT @Query = ..." nodes
  public override void Visit(SelectSetVariable node) {
    if ("@Query".Equals(node.Variable.Name, StringComparison.OrdinalIgnoreCase)) {
      QueryAssignments.Add(node.Expression);
    }
  }

  // Grab "SET @Query = ..." nodes
  public override void Visit(SetVariableStatement node) {
    if ("@Query".Equals(node.Variable.Name, StringComparison.OrdinalIgnoreCase)) {
      QueryAssignments.Add(node.Expression);
    }
  }

  // Grab "DECLARE @Query = ..." nodes
  public override void Visit(DeclareVariableElement node) {
    if (
      "@Query".Equals(node.VariableName.Value, StringComparison.OrdinalIgnoreCase) && 
      node.Value != null
    ) {
      QueryAssignments.Add(node.Value);
    }
  }
}

proceduresがストアド プロシージャ定義を持つとします。次に、次のList<string>ようにビジターを適用します。

foreach (string procedure in procedures) {
  TSqlFragment fragment;
  using (var reader = new StringReader(procedure)) {
    IList<ParseError> parseErrors;
    var parser = new TSql130Parser(true);  // or a lower version, I suppose
    fragment = parser.Parse(reader, out parseErrors);
    if (parseErrors.Any()) {
      // handle errors
      continue;
    }
  }
  var dynamicQueryFinder = new DynamicQueryFinder();
  fragment.Accept(dynamicQueryFinder);
  if (dynamicQueryFinder.QueryAssignments.Any()) {
    Console.WriteLine($"===== {dynamicQueryFinder.ProcedureName} =====");
    foreach (ScalarExpression assignment in dynamicQueryFinder.QueryAssignments) {
      Console.WriteLine(assignment.Script());
    }
  }
}

.Script()フラグメントをプレーンテキストに戻すことができるように、私が石畳にしたちょっとした便利な方法です。

public static class TSqlFragmentExtensions {
  public static string Script(this TSqlFragment fragment) {
    return String.Join("", fragment.ScriptTokenStream
      .Skip(fragment.FirstTokenIndex)
      .Take(fragment.LastTokenIndex - fragment.FirstTokenIndex + 1)
      .Select(t => t.Text)
    );
  }
}

これにより、 という名前の変数に割り当てられているすべてのストアド プロシージャのすべての式が出力されます@Query

このアプローチの良い点は、ステートメントを指先で解析できることです。そのため、文字列式をエスケープされていない形式に戻したり、and のすべてのインスタンスを探したりEXEC(...)(sp_executesql関連する変数名に関係なく) するなど、より複雑な処理も行うことができます。可能。

もちろん欠点は、これが純粋な T-SQL ではないことです。好きな .NET 言語を使用できますが (C# に最も慣れているため、C# を使用しました)、それでも外部コードを記述する必要があります。CHARINDEXすべてのコードが、T-SQL 文字列操作を分析するのに十分単純な特定のパターンに従っていることがわかっている場合は、文字列を単に ing するなどのより原始的なソリューションが機能する可能性があります。

于 2016-06-16T19:51:34.337 に答える