从SQL Server的视图/存储过程中提取连接信息。

huangapple go评论54阅读模式
英文:

Extracting join information from views/stored procs in SQL Server

问题

我有一个非常庞大的代码库需要浏览和文档化。目前,我正在通过查找存储过程/视图中的连接,并记录详细信息来进行文档化。这非常耗时。

我相当确定没有这种方法,但以防万一,是否有一种从SQL模块(视图/存储过程/函数/其他)中轻松获取连接信息的方法?

我知道 sys.dm_sql_referenced_entities,但它告诉你使用了什么,而不是连接方式。

另一种方法是编写一个SQL解析器以及相关内容,但我没有时间去做这个,也没有时间修改现成的解析器。

英文:

I have a very large code base to go through and document. At the moment I'm documenting joins by finding them in stored procedures / views and writing down the details. It's extremely time-consuming.

I'm pretty sure there isn't but just in case, is there a way of easily getting join information from within SQL modules (views/stored products/functions/whatever).

I'm aware of sys.dm_sql_referenced_entities but this tells you what's used, not how.

The alternative is to write a SQL parser and all that goes with it, but I don't have time to do that, nor to take an off-the-shelf one and modify that.

答案1

得分: 1

以下是翻译好的内容:

您可以查询数据库的系统表以获取对象定义。

这是一段SQL查询代码,用于搜索包含“JOIN”关键词的对象定义。这会查找到包括注释、列名等任何包含“JOIN”的地方。这是一个良好的起点,用于确定要检查哪些存储过程(而不是手动检查每个存储过程)。

在上述查询中:

[obj].[type] = 'P' OR [obj].[type] = 'V'

这使它仅查看(SQL)存储过程和视图。如果需要更多潜在的定义,您可以省略此条件。

现在,尝试从这些定义中获取关联信息。这可能会有点复杂,如果有更好的方法,那当然最好。

但是,您可以扫描存储过程的定义。示例中有一个用于说明的存储过程。接下来,我们可以使用这些知识来扫描系统表中的定义列。

由于这是SQL Server 2014(或2016年之前,SQL Server函数String_Split尚不存在)的情况,我们需要根据需要自行修改。基本方法来自这里,然后进行了修改。

接下来的代码是一个函数,用于将字符串拆分成表格:

IF OBJECT_ID('[dbo].[SPLIT_STRING]','IF') IS NULL BEGIN
    EXEC ('CREATE FUNCTION [dbo].[SPLIT_STRING] () RETURNS TABLE AS RETURN SELECT 1 X') 
END
GO
ALTER FUNCTION [dbo].[SPLIT_STRING]
(
    @string    nvarchar(MAX), 
    @separator nvarchar(MAX)
)
RETURNS TABLE WITH SCHEMABINDING 
AS RETURN
   WITH X(N) AS (SELECT 'Table1' FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) T(C)),
        Y(N) AS (SELECT 'Table2' FROM X A1, X A2, X A3, X A4, X A5, X A6, X A7, X A8) , -- Up to 16^8 = 4 billion
        T(N) AS (SELECT TOP(ISNULL(LEN(@string),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 N FROM Y),
        Delim(Pos) AS (SELECT t.N FROM T WHERE (SUBSTRING(@string, t.N, LEN(@separator+'x')-1) LIKE @separator OR t.N = 0)),
        Separated(value) AS (SELECT SUBSTRING(@string, d.Pos, LEAD(d.Pos,1,2147483647) OVER (ORDER BY (SELECT NULL)) - d.Pos - LEN(@separator)) 
                               FROM Delim d
                              WHERE @string IS NOT NULL)
       SELECT s.value
         FROM Separated s
        WHERE s.value <> @separator
GO

这个函数允许我们扫描定义,并查找包含关键词“JOIN”的部分,然后将其拆分为FROM和WHERE之间的内容。接着,我们再次筛选这些结果,以仅查看包含关键词“JOIN”的定义。

最后的查询在对象定义中分析短语。您可以更改SPLIT2 Cross Apply中的语句以查找其他内容。请注意,这会导致性能下降,因为涉及多次字符串拆分和文本扫描。

但是,我的结果将包括:

objName Type DefintionWithJoin
getFooBar P FROM dbo.ADDRESSES AS ADR INNER JOIN dbo.CLIENTS AS CLT ON ADR.ClientId = CLT.Id
getFooBar P FROM dbo.Clients AS CLT LEFT JOIN dbo.Users AS USR ON CLT.Id = USR.ClientId

这种方法可能需要根据关键字进行一些调整。首先查找至少包含一个JOIN语句的所有对象的第一个查询可用于双重检查结果。但上述查询在我的本地测试SQL Server 2014数据库中有效,其中包含约200个存储过程。

英文:

You can query the system tables of the database for object definitions.

SELECT DISTINCT [obj].[name] AS [Object_Name], [obj].[type], [obj].[type_desc], [mod].[definition]
FROM sys.sql_modules AS [mod]
INNER JOIN sys.objects AS [obj]
         ON [mod].[object_id] = [obj].[object_id]
WHERE [mod].definition Like &#39;%JOIN%&#39;
AND [obj].[type] = &#39;P&#39; OR [obj].[type] = &#39;V&#39;

This searches for simply the written word JOIN, if this for instance is used in a comment, is in a columnname or anything else it will also find it. But this should be a good start to know which procedures to check (instead of going through each one manually).

In the above query

> [obj].[type] = 'P' OR [obj].[type] = 'V'

Makes it only look at (SQL) Stored procedures and Views. You could omit this for more potential definitions. Msdn Link for Sys objects

Now to try and get join information from these definitions. It's going to be finnicky, and if someone knows a better method by all means.

But what you could do is scan the procedure definitions.
In my DB i have the following procedure for illustrative purposes:

CREATE PROCEDURE dbo.getFooBar AS
BEGIN
	SELECT * 
	FROM dbo.ADDRESSES AS ADR
	INNER JOIN dbo.CLIENTS AS CLT
		ON ADR.ClientId = CLT.Id
	WHERE ADR.City IS NOT NULL

	SELECT *
	FROM dbo.Clients AS CLT
	LEFT JOIN dbo.Users AS USR
		ON CLT.Id = USR.ClientId
	WHERE USR.Email IS NOT NULL
END

Now this is a fairly standard query, where you have a FROM block, which is followed by JOINS, and then a WHERE. We can use this knowledge to scan the definition column in our system table.

Because this is SQL Server 2014 (or pre 2016, where the SQL Server function String_Split does not exist yet) we tweak our own for this purpose. Base taken from here and then modified.

IF OBJECT_ID(&#39;[dbo].[SPLIT_STRING]&#39;,&#39;IF&#39;) IS NULL BEGIN
    EXEC (&#39;CREATE FUNCTION [dbo].[SPLIT_STRING] () RETURNS TABLE AS RETURN SELECT 1 X&#39;) 
END
GO
ALTER FUNCTION [dbo].[SPLIT_STRING]
(
    @string    nvarchar(MAX), 
    @separator nvarchar(MAX)
)
RETURNS TABLE WITH SCHEMABINDING 
AS RETURN
   WITH X(N) AS (SELECT &#39;Table1&#39; FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) T(C)),
        Y(N) AS (SELECT &#39;Table2&#39; FROM X A1, X A2, X A3, X A4, X A5, X A6, X A7, X A8) , -- Up to 16^8 = 4 billion
        T(N) AS (SELECT TOP(ISNULL(LEN(@string),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 N FROM Y),
        Delim(Pos) AS (SELECT t.N FROM T WHERE (SUBSTRING(@string, t.N, LEN(@separator+&#39;x&#39;)-1) LIKE @separator OR t.N = 0)),
        Separated(value) AS (SELECT SUBSTRING(@string, d.Pos, LEAD(d.Pos,1,2147483647) OVER (ORDER BY (SELECT NULL)) - d.Pos - LEN(@separator)) 
                               FROM Delim d
                              WHERE @string IS NOT NULL)
       SELECT s.value
         FROM Separated s
        WHERE s.value &lt;&gt; @separator
GO

This then allows us to scan through the definitions, scan for definitions that contain the word JOIN. Split those on the FROM (should be before JOIN). And then till the WHERE (or any other keyword). And then filter those results again to only look at definitions that once again have the JOIN keyword.

SELECT [SUBQUERY].[Object_Name], [SUBQUERY].[Type], [split2].[value] AS DefinitionWithJoin FROM
(SELECT DISTINCT [obj].[name] AS [Object_Name], [obj].[type], [mod].[definition]
FROM sys.sql_modules AS [mod]
INNER JOIN sys.objects AS [obj]
         ON [mod].[object_id] = [obj].[object_id]
WHERE [mod].definition Like &#39;%JOIN%&#39;
AND [obj].[type] = &#39;P&#39; OR [obj].[type] = &#39;V&#39;) AS SUBQUERY
CROSS APPLY dbo.[SPLIT_STRING](SUBQUERY.[definition],&#39;FROM&#39;) AS SPLIT1
CROSS APPLY dbo.[SPLIT_STRING](SPLIT1.value, &#39;WHERE&#39;) AS SPLIT2
WHERE SPLIT2.value LIKE &#39;%JOIN%&#39;

Essentially dissecting the object definition for phrases. You could change the statement in the SPLIT2 Cross Apply to be something else. Note that this is a decent performance hog, due to the various string splits, scanning text definitions.

But in my results I will get:

objName Type DefintionWithJoin
getFooBar P FROM dbo.ADDRESSES AS ADR INNER JOIN dbo.CLIENTS AS CLT ON ADR.ClientId = CLT.Id
getFooBar P FROM dbo.Clients AS CLT LEFT JOIN dbo.Users AS USR ON CLT.Id = USR.ClientId

Now this approach might need some tweaking based on keywords. And the first query that finds all objects that have at least 1 JOIN statements can be used to double check your results. But the above queries work on my local test SQL Server 2014 DB containing ~200 stored procedures.

huangapple
  • 本文由 发表于 2023年4月17日 22:38:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76036340.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定