C# 解析 SQL 语句以查找存储过程中使用的所有 INSERT/UPDATE/DELETE 表
Posted
技术标签:
【中文标题】C# 解析 SQL 语句以查找存储过程中使用的所有 INSERT/UPDATE/DELETE 表【英文标题】:C# parse SQL statement to find all INSERT/UPDATE/DELETE tables used in stored procedures 【发布时间】:2019-01-27 07:41:36 【问题描述】:正如标题所说,我的目的是找到所有参与 INSERT/UPDATE/DELETE 语句的表并生成结构化格式。到目前为止,这就是我想出的 -
void Main()
string DBName = "Blah";
string ServerName = @"(localdb)\MSSQLLocalDB";
Server s = new Server(ServerName);
Database db = s.Databases[DBName];
ConcurrentDictionary<string, SPAudit> list = new ConcurrentDictionary<string, SPAudit>();
var sps = db.StoredProcedures.Cast<StoredProcedure>()
.Where(x => x.ImplementationType == ImplementationType.TransactSql && x.Schema == "dbo")
.Select(x => new
x.Name,
Body = x.TextBody
).ToList();
Parallel.ForEach(sps, item =>
try
ParseResult p = Parser.Parse(item.Body);
IEnumerable<SqlInsertStatement> insStats = null;
IEnumerable<SqlUpdateStatement> updStats = null;
IEnumerable<SqlDeleteStatement> delStats = null;
var listTask = new List<Task>();
listTask.Add(Task.Run(() =>
insStats = FindBatchCollection<SqlInsertStatement>(p.Script.Batches);
));
listTask.Add(Task.Run(() =>
updStats = FindBatchCollection<SqlUpdateStatement>(p.Script.Batches);
));
listTask.Add(Task.Run(() =>
delStats = FindBatchCollection<SqlDeleteStatement>(p.Script.Batches);
));
Task.WaitAll(listTask.ToArray());
foreach (var ins in insStats)
var table = ins?.InsertSpecification?.Children?.FirstOrDefault();
if (table != null)
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("@"))
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
ll = new SPAudit();
ll.InsertTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
foreach (var ins in updStats)
var table = ins?.UpdateSpecification?.Children?.FirstOrDefault();
if (table != null)
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("@"))
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
ll = new SPAudit();
ll.UpdateTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
foreach (var ins in delStats)
var table = ins?.DeleteSpecification?.Children?.FirstOrDefault();
if (table != null)
var tableName = table.Sql.Replace("dbo.", "").Replace("[", "").Replace("]", "");
if (!tableName.StartsWith("@"))
var ll = list.ContainsKey(item.Name) ? list[item.Name] : null;
if (ll == null)
ll = new SPAudit();
ll.DeleteTable.Add(tableName);
list.AddOrUpdate(item.Name, ll, (key, old) => ll);
catch (Exception ex)
Console.WriteLine(ex.Message);
);
IEnumerable<T> FindBatchCollection<T>(SqlBatchCollection coll) where T : SqlStatement
List<T> sts = new List<T>();
foreach (var item in coll)
sts.AddRange(FindStatement<T>(item.Children));
return sts;
IEnumerable<T> FindStatement<T>(IEnumerable<SqlCodeObject> objs) where T : SqlStatement
List<T> sts = new List<T>();
foreach (var item in objs)
if (item.GetType() == typeof(T))
sts.Add(item as T);
else
foreach (var sub in item.Children)
sts.AddRange(FindStatement<T>(item.Children));
return sts;
public class SPAudit
public HashSet<string> InsertTable get; set;
public HashSet<string> UpdateTable get; set;
public HashSet<string> DeleteTable get; set;
public SPAudit()
InsertTable = new HashSet<string>();
UpdateTable = new HashSet<string>();
DeleteTable = new HashSet<string>();
现在我面临两个问题
首先,考虑到数据库中大约有 841 个存储过程,它需要花费大量时间才能完成。 其次,如果有以下类似的语句,则表名没有被正确捕获,这意味着该表被捕获为w
,而不是SomeTable_1
或SomeTable_2
。
CREATE PROCEDURE [dbo].[sp_blah]
@t SomeTableType READONLY
AS
DELETE w
FROM SomeTable_2 w
INNER JOIN (Select * from @t) t
ON w.SomeID = t.SomeID
DELETE w
FROM SomeTable_1 w
INNER JOIN (Select * from @t) t
ON w.SomeID = t.SomeID
RETURN 0
任何帮助将不胜感激。
编辑
使用此位置的以下 dll C:\Program Files (x86)\Microsoft SQL Server\140\DTS\Tasks
-
Microsoft.SqlServer.ConnectionInfo.dll
Microsoft.SqlServer.Management.SqlParser.dll
Microsoft.SqlServer.Smo.dll
Microsoft.SqlServer.SqlEnum.dll
【问题讨论】:
1) 您是否尝试过通过SQL 查询直接在SQL Server 上进行搜索? 2) 也考虑反过来。从表开始,查找所有使用该表的过程。 关于如何从表格列表开始的任何指针? 3) 您需要在动态查询或参数中满足表的需求吗? 如果可以,当然可以。 【参考方案1】:最后我让它像我希望输出看起来像使用@dlatikay 答案一样工作。我在这里发布这个更多是为了文档目的。
我正在使用以下 nuget 包 -
https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects/ https://www.nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom/并删除了所有其他本地依赖项。我希望这对那里的人有所帮助。
void Main()
string DatabaseName = "Blah";
string ServerIP = @"(localdb)\MSSQLLocalDB";
List<string> ExcludeList = new List<string>()
"sp_upgraddiagrams",
"sp_helpdiagrams",
"sp_helpdiagramdefinition",
"sp_creatediagram",
"sp_renamediagram",
"sp_alterdiagram",
"sp_dropdiagram"
;
List<string> StringDataTypes = new List<string>()
"nvarchar",
"varchar",
"nchar",
"char",
;
Server s = new Server(ServerIP);
s.SetDefaultInitFields(typeof(StoredProcedure), "IsSystemObject");
Database db = s.Databases[DatabaseName];
Dictionary<string, SPAudit> AuditList = new Dictionary<string, SPAudit>();
var sps = db.StoredProcedures.Cast<StoredProcedure>()
.Where(x => x.ImplementationType == ImplementationType.TransactSql && x.Schema == "dbo" && !x.IsSystemObject)
.Select(x => new
x.Name,
Body = x.TextBody,
Parameters = x.Parameters.Cast<StoredProcedureParameter>().Select(t =>
new SPParam()
Name = t.Name,
DefaultValue = t.DefaultValue,
DataType = $"t.DataType.Name(StringDataTypes.Contains(t.DataType.Name) ? $"((t.DataType.MaximumLength > 0 ? Convert.ToString(t.DataType.MaximumLength) : "MAX"))" : "")"
)
).ToList();
foreach (var item in sps)
try
TSqlParser parser = new TSql140Parser(true, SqlEngineType.Standalone);
IList<ParseError> parseErrors;
TSqlFragment sqlFragment = parser.Parse(new StringReader(item.Body), out parseErrors);
sqlFragment.Accept(new OwnVisitor(ref AuditList, item.Name, item.Parameters));
catch (Exception ex)
//Handle exception
public class OwnVisitor : TSqlFragmentVisitor
private string spname;
private IEnumerable<SPParam> parameters;
private Dictionary<string, SPAudit> list;
public OwnVisitor(ref Dictionary<string, SPAudit> _list, string _name, IEnumerable<SPParam> _parameters)
list = _list;
spname = _name;
parameters = _parameters;
public override void ExplicitVisit(InsertStatement node)
NamedTableReference namedTableReference = node?.InsertSpecification?.Target as NamedTableReference;
if (namedTableReference != null)
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
if (!list.ContainsKey(spname))
SPAudit ll = new SPAudit();
ll.InsertTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
else
SPAudit ll = list[spname];
ll.InsertTable.Add(table);
base.ExplicitVisit(node);
public override void ExplicitVisit(UpdateStatement node)
NamedTableReference namedTableReference;
if (node?.UpdateSpecification?.FromClause != null)
namedTableReference = node?.UpdateSpecification?.FromClause?.TableReferences[0] as NamedTableReference;
else
namedTableReference = node?.UpdateSpecification?.Target as NamedTableReference;
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
if (!list.ContainsKey(spname))
SPAudit ll = new SPAudit();
ll.UpdateTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
else
SPAudit ll = list[spname];
ll.UpdateTable.Add(table);
base.ExplicitVisit(node);
public override void ExplicitVisit(DeleteStatement node)
NamedTableReference namedTableReference;
if (node?.DeleteSpecification?.FromClause != null)
namedTableReference = node?.DeleteSpecification?.FromClause?.TableReferences[0] as NamedTableReference;
else
namedTableReference = node?.DeleteSpecification?.Target as NamedTableReference;
if (namedTableReference != null)
string table = namedTableReference?.SchemaObject.BaseIdentifier?.Value;
if (!string.IsNullOrWhiteSpace(table) && !table.StartsWith("#"))
if (!list.ContainsKey(spname))
SPAudit ll = new SPAudit();
ll.DeleteTable.Add(table);
ll.Parameters.AddRange(parameters);
list.Add(spname, ll);
else
SPAudit ll = list[spname];
ll.DeleteTable.Add(table);
base.ExplicitVisit(node);
public class SPAudit
public HashSet<string> InsertTable get; set;
public HashSet<string> UpdateTable get; set;
public HashSet<string> DeleteTable get; set;
public List<SPParam> Parameters get; set;
public SPAudit()
InsertTable = new HashSet<string>();
UpdateTable = new HashSet<string>();
DeleteTable = new HashSet<string>();
Parameters = new List<SPParam>();
public class SPParam
public string Name get; set;
public string DefaultValue get; set;
public string DataType get; set;
【讨论】:
【参考方案2】:SMO 模型公开了语法树的元素。因此,不要像在
中那样按位置假设一个标记UpdateSpecification?.Children?.FirstOrDefault();
查找对应的属性in the documentation。对于更新子句,目标表(或可更新视图)可以出现在不同的位置。采用这种语法:
UPDATE tablename SET column=value WHERE conditions
表示为
var targettable = ins?.UpdateSpecification?.Target?.ScriptTokenStream?.FirstOrDefault()?.Text;
在 SMO 模型中。而 tsql 独有的语法,
UPDATE t SET t.columnname=value FROM tablename t WHERE conditions
将在FROM clause 中拥有其表列表。
关于您提到的其他两个 DML 语句:DELETE
是相同的,因为它们共享一个公共基类 DeleteInsertSpecification
(Target
)。
对于INSERT
,还有Target
,如果它的InsertSource
是SelectInsertSource
类型,这也可以基于任意数量的表和视图。
【讨论】:
INSERT/DELETE 也一样吗?您的代码也没有为我编译,因为它找不到ScriptTokenStream
。
在我在问题中提到的位置(已编辑)中似乎找不到Microsoft.SqlServer.TransactSql.ScriptDom.dll
。
这是一个完全不同的问题。那么原始代码完全编译了吗?我指的是140.17283.0版本的SMO组件
必须更改我的代码才能使用此TSqlParser parser = new TSql120Parser(true); IList<ParseError> parseErrors; TSqlFragment sqlFragment = parser.Parse(new StringReader(sql), out parseErrors);
【参考方案3】:
您可以使用以下 SQL 查询:
SELECT *
FROM sys.dm_sql_referenced_entities ('dbo.APSP_MySP', 'OBJECT');
它为您提供存储过程中受影响的所有表、视图和 SP。 is_selected 或 is_select_all 设置为 1 用于选定的参考 is_updated 设置为 1 用于更新引用
由于查询是从预定义的系统表中读取的,因此运行速度很快
如果您需要有关被引用对象的信息,请使用 referenced_id 列值来查找详细信息
您可以通过两种方式使用它:
-
为每个存储过程并行调用上述查询
创建另一个查询/SP,它将为每个存储过程循环并运行它
【讨论】:
这不会让我得到我想要得到的数据集。 @SohamDasgupta:你在寻找什么价值观?我可能会建议其他方法 我要查找的数据集是一个表,其中包含以下列 -SPName,InsertTable,UpdateTable,DeleteTable
,不知道如何从您的解决方案中得出这个结论。
@SohamDasgupta:表名在“referenced_entity_name”列中。如果 action 为 Select,则“is_selected”或“is_select_all”列的值为“1”。如果操作是更新或删除,则“is_updated”列的值为“1”。请在此处查看--------> docs.microsoft.com/en-us/sql/relational-databases/…【参考方案4】:
-
将 Proc_1 更改为您的过程名称
优化 PATINDEX 匹配以适应不同的可能性
修改以查看所有程序
不适合动态 sql 中的表或作为参数传递的表
注意 dm_sql_referenced_entities 的任何问题
SELECT
e.TableName,
p.name,
PATINDEX('%DELETE '+e.TableName+'%', p.definition) AS is_delete,
PATINDEX('%INSERT INTO '+e.TableName+'%', p.definition) AS is_insert,
PATINDEX('%UPDATE '+e.TableName+'%', p.definition) AS is_update
FROM
(
SELECT distinct referenced_entity_name AS TableName
FROM sys.dm_sql_referenced_entities ('dbo.Proc_1', 'OBJECT')
) e,
(
SELECT o.name, m.object_id, definition
FROM sys.objects o, sys.sql_modules m
WHERE o.name = 'Proc_1'
AND o.type='P'
AND m.object_id = o.object_id
) p
【讨论】:
【参考方案5】:我建议您查询 syscmets SQL 视图。性能会好很多。
select text from sys.syscomments where text like '%DELETE%'
您可以在 SQL Query 中处理结果或获取所有结果并在 C# 中过滤数据。
【讨论】:
试过了,当你有像Date_Update
这样的列时,误报太多了。
在update关键字前加个空格怎么样?其中像“% UPDATE%”这样的文本以上是关于C# 解析 SQL 语句以查找存储过程中使用的所有 INSERT/UPDATE/DELETE 表的主要内容,如果未能解决你的问题,请参考以下文章