Verify that specific joins are made everywhere in a query

浪子不回头ぞ 提交于 2021-02-15 07:39:17

问题


I have to go through hundreds of queries in the form of stored procedures and verify that for every join being made that:

  1. a specific column join is always made in the join
  2. that the join doesn't exist in a hardcoded format to the previous only value of the column (i.e it needs to be like a.requiredJoinColumn = b.requiredJoinColumn and not a.requiredJoinColumn = 'onlyValue'

For example if the required column was named 'reqCol' I'd want to find this as a problem:

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

also,

SELECT a.* 
FROM tableA a
JOIN table b ON a.reqCol = b.reqCol
JOIN table c ON a.OtherColumn = c.otherColumn

also, I'd want this to not show up as a problem

SELECT a.* 
FROM tableA a
JOIN table b ON a.reqCol = b.correctColButDifferentName

I'd also need it to work on explicitly stated inner and outer joins as well and also in cases where the joins are done via commas (i.e select * from tableA, tableB where a.OtherColumn = b.OtherColumn)

Right now I'm going about this manually and it's taking forever so I was hoping there might be a tool that I could use. Maybe something I could write some validation logic into or with and execute across a series of stored procedures in my SQL Server database.


回答1:


You could "export" the sql_modules and feed them to a sql parser or you could bring a parser in sql server and handle them internally (a bit unorthodox but "creative" at the same time).

There is an smo parser in .Net and it could be used in a clr module (eg. a scalar function)

//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;
        }
    }
}

Build the dll and copy to the build location the following two dlls:

Microsoft.SqlServer.Management.SqlParser.dll

Microsoft.SqlServer.Diagnostics.STrace.dll

create the assembly & the clr function:

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;

You will have to go through the parser's xml structure and find a way through it to get what you need.

For a kick-start (and inspiration):

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);

The above, will produce a result set similar to the following (excerpt for modules in msdb)

| 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                                    |


来源:https://stackoverflow.com/questions/60196318/verify-that-specific-joins-are-made-everywhere-in-a-query

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!