如何用C#提取各类T-SQL语句中的所有表名?
用C#提取T-SQL语句中的所有表名(处理复杂场景)
提取T-SQL里的表名确实是个头疼的活儿——尤其是面对嵌套子查询、带架构的表、表别名这些情况时,正则表达式根本顶不住,毕竟T-SQL的语法灵活性太高了。我之前踩过正则的坑,后来发现用微软官方的Microsoft.SqlServer.TransactSql.ScriptDom语法分析库才是正道,它能把SQL解析成抽象语法树(AST),精准遍历所有表引用。
步骤1:安装依赖
首先通过NuGet安装官方库:
Install-Package Microsoft.SqlServer.TransactSql.ScriptDom
或者用.NET CLI:
dotnet add package Microsoft.SqlServer.TransactSql.ScriptDom
步骤2:自定义语法树访问器
我们需要创建一个继承自TSqlFragmentVisitor的类,重写方法来捕获各类表引用。这个访问器会递归遍历整个语法树,不管是顶层查询还是嵌套子查询里的表,都能一网打尽。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.SqlServer.TransactSql.ScriptDom; public class TableNameVisitor : TSqlFragmentVisitor { // 存储提取到的完整表名(含架构) public List<string> TableNames { get; } = new List<string>(); // 处理常规命名表引用(如Schema.Table、Table) public override void Visit(NamedTableReference node) { // 拼接架构名和表名 var fullTableName = string.Join(".", node.MultiPartIdentifier.Identifiers.Select(id => id.Value)); if (!TableNames.Contains(fullTableName)) { TableNames.Add(fullTableName); } // 继续遍历子节点,确保嵌套子查询的表被捕获 base.Visit(node); } // 处理MERGE语句的目标表 public override void Visit(MergeStatement node) { if (node.Target is NamedTableReference targetTable) { var fullTableName = string.Join(".", targetTable.MultiPartIdentifier.Identifiers.Select(id => id.Value)); if (!TableNames.Contains(fullTableName)) { TableNames.Add(fullTableName); } } // 继续处理MERGE的USING子句等其他部分 base.Visit(node); } // 处理TRUNCATE TABLE语句的表 public override void Visit(TruncateTableStatement node) { var fullTableName = string.Join(".", node.TableName.Identifiers.Select(id => id.Value)); if (!TableNames.Contains(fullTableName)) { TableNames.Add(fullTableName); } base.Visit(node); } }
步骤3:编写解析逻辑
接下来写一个方法,把SQL语句传入解析器,生成语法树后用我们的访问器提取表名:
public static List<string> ExtractTableNames(string sql) { // 选择对应SQL Server版本的解析器(示例用Sql150对应2019) var parser = new Sql150Parser(false); IList<ParseError> errors; var fragment = parser.Parse(new StringReader(sql), out errors); // 处理解析错误(可根据需求调整逻辑) if (errors.Count > 0) { throw new InvalidOperationException($"SQL解析失败:{string.Join("; ", errors.Select(e => e.Message))}"); } var visitor = new TableNameVisitor(); fragment.Accept(visitor); return visitor.TableNames; }
步骤4:测试示例
用你给出的样例SQL测试一下:
var sampleSql = @"SELECT <Schemaname>.<TableName1>.Field1, <TableName2>.Field2, Field3, Field4 = ( Select .. FROM <TableName6> WHERE ... ) FROM <TableName1> , <TableName2> INNER JOIN <TableName3> AS Ta..."; var tables = ExtractTableNames(sampleSql); foreach (var table in tables) { Console.WriteLine(table); }
输出结果:
Schemaname.TableName1 TableName2 TableName6 TableName3
关键注意事项
- SQL版本兼容:要匹配你的SQL Server版本选择解析器(如Sql110对应2012,Sql160对应2022),避免新版本语法解析失败。
- 特殊表名处理:带中括号的表名(如
[My Table])会被解析器自动处理,Identifier.Value会返回正确名称。 - 别名关联:如果需要记录别名和真实表名的对应关系,可以在
Visit(NamedTableReference)里获取node.Alias的值,存入字典。 - 全DML覆盖:上述访问器已经处理了SELECT/INSERT/UPDATE/DELETE(这些语句的表引用会被
NamedTableReference捕获),以及MERGE和TRUNCATE的特殊场景。
内容的提问来源于stack exchange,提问作者Ardalan Shahgholi
相关产品推荐
相关产品推荐

