Sql server 验证是否在查询中的任何位置都进行了特定联接

Sql server 验证是否在查询中的任何位置都进行了特定联接,sql-server,Sql Server,我必须以存储过程的形式进行数百次查询,并验证对于进行的每个连接: 特定的列联接始终在联接中进行 连接不以硬编码格式存在于列的前一个唯一值(即它需要类似于a.requiredJoinColumn=b.requiredJoinColumn,而不是a.requiredJoinColumn='onlyValue' 例如,如果所需的列名为'reqCol',我希望发现这是一个问题: SELECT a.* FROM tableA a JOIN table b ON a.OtherColumn = b.Oth

我必须以存储过程的形式进行数百次查询,并验证对于进行的每个连接:

  • 特定的列联接始终在联接中进行
  • 连接不以硬编码格式存在于列的前一个唯一值(即它需要类似于a.requiredJoinColumn=b.requiredJoinColumn,而不是a.requiredJoinColumn='onlyValue'
  • 例如,如果所需的列名为'reqCol',我希望发现这是一个问题:

    SELECT a.* 
    FROM tableA a
    JOIN table b ON a.OtherColumn = b.OtherColumn
    
    而且

    另外,我希望这不会成为一个问题

    SELECT a.* 
    FROM tableA a
    JOIN table b ON a.reqCol = b.correctColButDifferentName
    
    我还需要它处理显式声明的内部联接和外部联接,以及通过逗号进行联接的情况(即从表a中选择*,表b中a.OtherColumn=b.OtherColumn)

    现在我正在手动进行这项工作,这需要花费很长时间,所以我希望有一个工具可以使用。也许我可以在SQL Server数据库中的一系列存储过程中写入或使用一些验证逻辑并执行。

    您可以“导出”sql_模块并将它们提供给sql解析器,或者您可以在sql server中引入解析器并在内部处理它们(有点非正统,但同时具有“创造性”)

    有一个,可以在clr模块中使用(例如标量函数)

    生成dll并将以下两个dll复制到生成位置:

    Microsoft.SqlServer.Management.SqlParser.dll

    Microsoft.SqlServer.Diagnostics.STrace.dll

    创建程序集和clr函数:

    create assembly sqlparse from 'C:\path to the project\bin\Debug\xyz.dll'
    with permission_set = unsafe;
    go
    
    create function dbo.parseSqlToXml(@sql nvarchar(max))
    returns nvarchar(max)
    with execute as caller, returns null on null input
    as
    external name [sqlparse].[sqlns.SQLParser].SQLParseToXml;
    
    您必须检查解析器的xml结构,并找到一种方法来获得所需的内容

    对于启动(和灵感):

    上述内容将产生类似于以下内容的结果集(msdb中模块的摘录)


    真的没有一种方法可以自动完成这项工作。只是有很多可能性。任何类型的自动化工具或查询都可能会返回许多误报,同时会漏掉几个违规者。如果我最终写了这篇文章,我可能会从查询、表、联接和列变量开始,并且在分析查询时,我会导航通过连接列表,确保每个表中的一个连接列引用了我所需的列。我知道可能存在某些情况(如内部选择)虽然这并不容易,但在我的几个简单例子中,我认为很容易找到这些场景。你会同意,对于这些简单的场景,它不应该是一个问题,或者你看到的是不同的吗?即使是简单的例子,这也几乎是不可能的。ferent或有多个连接谓词。那么“连接列表”呢?它们可以是交叉、左、右、外部、隐含交叉连接。如何定义仅从查询中涉及的表?同义词呢?或者链接服务器上的远程表呢?你可能会花两年时间编写一个工具来实现这一点,但仍然会错过“简单”这个词一个。关于表值函数呢?列表层出不穷。SSMS知道什么是表,什么不是表,对吗?它还知道什么是列和注释。它了解同义词,知道交叉连接和显式连接之间的区别,真的,所有这些。我想你是对的,我不想写SSMS,但我希望可能有一个VS或SQL的框架或扩展或其他东西,可以给我SSMS已经知道的信息。我认为你唯一正确的是列名不同。这需要我进行一些配置。你说的是查询,对吗?这意味着它要么是存储过程,要么是视图,要么是函数。或者可能传递sql。让我们保持简单,只使用存储过程。如何查找该过程中连接的所有表?可以将sys.sysdepends视为一个很好的起始位置。但是,然后呢?您必须解析过程的文本,以查找规则。这就是它开始的地方变得异常困难。关键是你会找到一些,但要找到它们太复杂了。
    //r: Microsoft.SqlServer.Management.SqlParser.dll
    using System;
    using Microsoft.SqlServer.Server;
    using Microsoft.SqlServer.Management.SqlParser.Parser;
    using System.Reflection;
    
    
    namespace sqlns
    {
        public partial class SQLParser
        {
            [SqlFunction(DataAccess = DataAccessKind.None)]
            [return: SqlFacet(MaxSize = -1)]
            public static string SQLParseToXml(string sqlquery)
            {
                if (string.IsNullOrEmpty(sqlquery))
                {
                    return sqlquery;
                }
    
                ParseResult pres = Parser.Parse(sqlquery);
                Object script = pres.GetType().GetProperty("Script", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(pres, null);
                String xmlstr = script.GetType().BaseType.GetProperty("Xml").GetValue(script, null).ToString();
                return xmlstr;
            }
        }
    }
    
    create assembly sqlparse from 'C:\path to the project\bin\Debug\xyz.dll'
    with permission_set = unsafe;
    go
    
    create function dbo.parseSqlToXml(@sql nvarchar(max))
    returns nvarchar(max)
    with execute as caller, returns null on null input
    as
    external name [sqlparse].[sqlns.SQLParser].SQLParseToXml;
    
    select src.modulename,  
        t.col.value('../comment()[1]', 'nvarchar(500)') as joincondition,
        replace(left(t.col.value('(..//*/@Location)[1]', 'varchar(20)'), charindex(',', t.col.value('(..//*/@Location)[1]', 'varchar(20)'))), '(', '') as linenumber, 
        t.col.value('./comment()[1]', 'nvarchar(500)') as columncondition,
        t.col.value('(./SqlScalarRefExpression[1]/@ColumnOrPropertyName)[1]', 'nvarchar(200)') as leftcol,
        t.col.value('(./SqlScalarRefExpression[2]/@ColumnOrPropertyName)[1]', 'nvarchar(200)') as rightcol,
        t.col.value('(./SqlLiteralExpression[1]/@Value)[1]', 'nvarchar(200)') as literal
    from 
    (
        select object_name(object_id) as modulename, cast(dbo.parseSqlToXml(definition) as xml) as definitionxml
        from sys.sql_modules
    ) as src
    cross apply src.definitionxml.nodes('//SqlQualifiedJoinTableExpression/SqlConditionClause//SqlComparisonBooleanExpression') as t(col);
    
    | modulename                                     | joincondition                                                             | linenumber | columncondition                                 | leftcol              | rightcol      | literal                              |
    |------------------------------------------------|---------------------------------------------------------------------------|------------|-------------------------------------------------|----------------------|---------------|--------------------------------------|
    | syscollector_execution_log_full                | (p.id = t.package_id AND p.id != N'84CEC861-D619-433D-86FB-0BB851AF454A') | 25,        | p.id != N'84CEC861-D619-433D-86FB-0BB851AF454A' | id                   | NULL          | 84CEC861-D619-433D-86FB-0BB851AF454A |
    | sp_syscollector_delete_execution_log_tree      | ON (node.log_id = leaf.parent_log_id)                                     | 25,        | (node.log_id = leaf.parent_log_id)              | log_id               | parent_log_id | NULL                                 |
    | sp_syscollector_delete_execution_log_tree      | ON (l.package_execution_id = s.executionid)                               | 34,        | (l.package_execution_id = s.executionid)        | package_execution_id | executionid   | NULL                                 |
    | sp_syscollector_delete_execution_log_tree      | ON i.log_id = l.log_id                                                    | 35,        | i.log_id = l.log_id                             | log_id               | log_id        | NULL                                 |
    | sp_syscollector_delete_execution_log_tree      | ON i.log_id = l.log_id                                                    | 40,        | i.log_id = l.log_id                             | log_id               | log_id        | NULL                                 |
    | sp_syscollector_delete_collection_set_internal | ON (cs.schedule_uid = sv.schedule_uid)                                    | 29,        | (cs.schedule_uid = sv.schedule_uid)             | schedule_uid         | schedule_uid  | NULL                                 |
    | sysutility_mi_configuration                    | ON 1=1                                                                    | 11,        | 1=1                                             | NULL                 | NULL          | 1                                    |