大致来说,这是在 C# 中使用ScriptDom
.
获取所有存储过程定义的列表很容易。这可以在 T-SQL 中完成,甚至:
sp_msforeachdb 'select definition from [?].sys.sql_modules'
或者以通常的方式编写数据库脚本,或者使用 SMO。无论如何,我假设您可以以List<string>
某种方式将它们放入其中,以供代码使用。
Microsoft.SqlServer.TransactSql.ScriptDom
可作为NuGet包使用,因此请将其添加到全新的应用程序中。我们问题的核心是编写一个访问者,它将从 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
。
这种方法的好处是您可以轻松解析语句,因此更复杂的处理,例如将字符串表达式转回其未转义形式或寻找EXEC(...)
and的所有实例sp_executesql
(不管涉及的变量名),也是可能的。
当然,缺点是这不是纯 T-SQL。您可以使用任何您喜欢的 .NET 语言(我使用过 C#,因为我最熟悉它),但它仍然涉及编写外部代码。CHARINDEX
如果您知道所有代码都遵循一种对 T-SQL 字符串操作来说足够简单的特定模式来分析,那么更原始的解决方案(例如仅遍历字符串)可能会起作用。