使用简单正则表达式和一些算法魔法进行SQL对象依赖关系分析
一个简单而强大的工具,可以相当快速可靠地分析 SQL 对象依赖关系。
引言
数据库诞生于 70 年代。从那时起,它们变得越来越大,逻辑越来越复杂,并且仍在不断增长。因此,有许多工具,SQL 解析器,可以按对象分词脚本。
但是,常见的解析器 (例如 ANTLR) 可能会遇到脚本解析错误,每种 SQL 方言都有其特殊性,并且在处理大量数据库对象时会遇到时间问题。
因此,我想展示一种更简单的方法,即结合使用正则表达式和一些技巧。
主要概念
- 获取整个数据库对象的单文件 SQL 脚本。
- 使用匹配 DDL 语句的正则表达式将 SQL 脚本分割成文本块
- 在这些文本块中搜索某个文本字符串
实现
1. 获取所有 SQL 对象脚本
每个 RDBMS 都有一个生成 SQL drop/create 脚本的选项。因此,首先我们获取所有数据库对象的单个 SQL 脚本。或者我们可以使用 northwind.sql 作为示例。
2. 使用正则表达式获取所有 DDL 语句
我使用此表达式
\b(create|alter)\s+(unique)*\s*(nonclustered)*\s*
(procedure|proc|table|trigger|view|function|index)\b\s([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*
FastColoredTextBox 提供了强大的正则表达式处理工具,因此我们使用
var result = range.GetRangesByLines(regexStr, RegexOptions.IgnoreCase);
首先,我构建了一些 有用的正则表达式 并将它们收集到一个 static 类中
    public static class RegexValues
    {
        public static string SqlCmdObjects = @"\[\$\(([^=<>\[\]\s\']+)\)\].\[[^=<>\s\']+\]";
        public static string SqlCmdObjectsShort = @"\$\(([^=<>\[\]\s\']+)\)";
        public static string DdlObjects = @"\b(create)\s+(procedure|proc|table|trigger|
               view|function)\b\s\[\$\(([^=<>\[\]\s\']+)\)\].\[[^=<>\s\']+\]";
        public static string DdlObjectsPrepared = @"\b(create)\s+(procedure|proc|table|
               trigger|view|function)\b\s\[([^=<>\[\]\s\']+)\].(\[[^=<>\s\']+\])";
        public static string DdlObjects_ = @"\b(create)\s+(procedure|proc|table|
                      trigger|view|function)\b\s\$\(([^=<>\[\]\s\']+)\).[^=<>\s\']+";
  
        public static string DdlObjectsPrepared_ = @"\b(create)\s+(procedure|proc|table|
                      trigger|view|function)\b\s([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
        public static string DdlObjectsPreparedProcViewTrigger = @"\b(create)\s+
        (procedure|proc|trigger|view|function)\b\s([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
        public static string DdlObjectsPreparedWithIndex = @"\b(create)\s+(unique)*\s*
        (nonclustered)*\s*(procedure|proc|table|trigger|view|function|index)\b\s
        ([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
        public static string DdlIndexAll = @"\b(create|alter)\s+(procedure|proc|table|
                      trigger|view|function)\b\s\[([^=<>\[\]\s\']+)\].\[[^=<>\s\']+\]";
        public static string Variables = @"\@([^=<>\s\'\)\(\,]+)";
        public static string SqlCmdVariables = @"\:SETVAR\s+([a-zA-Z_]+)\s+([a-zA-Z_]+)";
    }
- SqlCmdObjects- 匹配- [$(some_var)].[obj_name]或- [$(some_var)].[obj_schema].[obj_name]
- SqlCmdObjectsShort- 匹配- $(some_var)
- DdlObjects- 与- SqlCmdObjects相同,但增加了- PROC/- TABLE/- VIEW/- FUNC/- TRIGGER语句
- DdlObjectsPrepared- 与- DdlObjects相同,但移除了- $(- sqlcmd变量) 限制
- DdlObjects_- 与- DdlObjects相同,但移除了包围的- []限制
- DdlObjectsPrepared_- 与- DdlObjectsPrepared相同,但移除了包围的- []限制
- DdlObjectsPreparedProcViewTrigger- 仅用于 proc、views、triggers 和 functions 的 DDL 语句,即仅代码对象
- DdlObjectsPreparedWithIndex- 与- DdlObjectsPrepared_相同,但增加了- index语句
- DdlIndexAll- 与- DdlObjectsPrepared_相同,但增加了- alter语句
- Variables- 查找脚本中所有- @variables的正则表达式
- SqlCmdVariables- 查找 SQL CMD 变量 (例如- :SETVAR var1 val1)
该软件的核心是以下几行代码
    var range = Syncronized(() => fastColoredTextBox1.Selection.Clone());
    range.Normalize();
    range.Start = new Place(0, 0);
    range.End = Syncronized(() => new Place(tb.GetLineLength(tb.LinesCount - 1), 
                                            tb.LinesCount - 1));  
因此,我们只需将 SQL 脚本加载到 FastColoredTextBox (非常感谢其作者!!!) 中,然后将一些正则表达式应用于其所有内容。作为输出,我们得到了 RESULT 变量,其中包含找到的范围列表。
Range 是一个强大的类 (再次感谢 FastColoredTextBox 的作者)。Range 包含 SQL 脚本中找到的片段的行号和列号。我们只是将范围列表存储在虚拟 listbox (通用技术) 中,并在 SelectedIndexChanged 时执行以下操作
    fastColoredTextBox1.Selection = range;
    fastColoredTextBox1.DoSelectionVisible();
通过这两行,我们立即导航到找到的代码片段 (即 DDL 语句)。
然后我们构建另一个范围列表,但不是使用正则表达式字符串,而是放入要查找的对象的名称。
最后一步是在第一个范围列表中查找第二个范围列表的出现。这在方法中完成
private void FindUsage(string regex)
在 Form1.cs 中。
就是这样,其余的工作就是通用的 .NET 编码来将所有东西组合在一起。
简而言之,上述逻辑如下:
- 我们使用正则表达式查找所有 create/alter语句。并记住那些行号。
- 我们使用正则表达式查找文本 (对象,例如表名) 的所有出现。并且也记住行号。
- 现在将这两个数据集连接起来,查找文本行位于 create/alter语句行之间的位置。
结果是,我们得到了一个简单的 GUI 设计器,您可以在其中
- 打开 SQL 脚本或整个文件夹 (脚本将被合并到一个 temp 文件中并作为单个脚本打开)
- 搜索所有 DDL 语句 (CREATE/ALTER) - 列表将在左侧窗格中构建,并提供完整的导航支持。
- 用鼠标选择一段文本 (例如,某个 SQL 对象名称)
- 右键单击并选择 "查找用法" - 列表将在右侧窗格中构建 - 这些是包含所选文本的 SQL 对象
另一个优点是,您不仅可以搜索 SQL 对象在 SQL 对象中的出现,还可以搜索几乎任何代码片段、文本、注释等。
重要提示
由于这种分析 SQL 依赖关系的方法不是基于元数据信息,因此您应该始终牢记,您正在连接两个行数据集。所以有一些限制,或者更确切地说,“特性” :)
假设我们有一个存储过程
create proc test
as
declare @somevar int
create table #tmp(
colum_we_search nvarchar(255),
somevar int)
如果我们应用匹配 "CREATE TABLE" 语句的正则表达式,我们将匹配 temp 表以及 CREATE PROC。
然后,如果我们尝试搜索 "COLUMN_WE_SEARCH" - 它将在那个 temp 表 #tmp 中找到,而不是在过程 test 中。
这可以通过更精确的初始正则表达式来解决。对于这种情况,我编写了 DdlObjectsPreparedProcViewTrigger 正则表达式语句 (见上文)。
关注点
我想添加 MSAGL https://github.com/Microsoft/automatic-graph-layout 支持来可视化依赖关系。
FastColoredTextBox - 是高端的,是完全的突破!我甚至没料到它会有这么多很酷的功能!
历史
- 2018 年 1 月 10 日:初始版本


