7

有没有办法获取 SQL 查询中使用的表列表?示例:我有类似的东西:

SELECT * FROM Table t JOIN OtherTable ON t.id=OtherTable.t_id

我希望得到

Table, OtherTable

谢谢

4

5 回答 5

10

使用 C# 的一种解决方案是导入Microsoft.SqlServer.TransactSql.ScriptDom(我在 找到了 dll C:\Program Files (x86)\Microsoft SQL Server\120\SDK\Assemblies\Microsoft.SqlServer.TransactSql.ScriptDom.dll)然后执行以下操作:

private List<string> GetTableNamesFromQueryString(string query)
{
    IList<ParseError> errors = new List<ParseError>();
    IList<TSqlParserToken> queryTokens;
    List<string> output = new List<string>(16);
    StringBuilder sb = new StringBuilder(128);
    TSql120Parser parser = new TSql120Parser(true);
    TSqlTokenType[] fromTokenTypes = new TSqlTokenType[2]
        {
            TSqlTokenType.From,
            TSqlTokenType.Join
        };
    TSqlTokenType[] identifierTokenTypes = new TSqlTokenType[2]
        {
            TSqlTokenType.Identifier,
            TSqlTokenType.QuotedIdentifier
        };

    using (System.IO.TextReader tReader = new System.IO.StringReader(query))
    {
        queryTokens = parser.GetTokenStream(tReader, out errors);
        if (errors.Count > 0) { return errors.Select(e=>"Error: " + e.Number + " Line: " + e.Line + " Column: " + e.Column + " Offset: " + e.Offset + " Message: " + e.Message).ToList(); }

        for (int i = 0; i < queryTokens.Count; i++)
        {
            if(fromTokenTypes.Contains(queryTokens[i].TokenType))
            {
                for (int j = i + 1; j < queryTokens.Count; j++)
                {
                    if (queryTokens[j].TokenType == TSqlTokenType.WhiteSpace) { continue; }
                    else if (identifierTokenTypes.Contains(queryTokens[j].TokenType))
                    {
                        sb.Clear();

                        GetQuotedIdentifier(queryTokens[j], sb);            //Change Identifiers to QuotedIdentifier (text only)

                        while (j + 2 < queryTokens.Count && queryTokens[j + 1].TokenType == TSqlTokenType.Dot && identifierTokenTypes.Contains(queryTokens[j + 2].TokenType))
                        {
                            sb.Append(queryTokens[j + 1].Text);
                            GetQuotedIdentifier(queryTokens[j + 2], sb);    //Change Identifiers to QuotedIdentifier (text only)

                            j += 2;
                        }

                        output.Add(sb.ToString());
                        break;              //exit the loop
                    }
                    else { break; }             //exit the loop if token is not a FROM, a JOIN, or white space.
                }

            }
        }

        return output.Distinct().OrderBy(tableName => tableName).ToList();
    }
}

private void GetQuotedIdentifier(TSqlParserToken token, StringBuilder sb)
{
    switch(token.TokenType)
    {
        case TSqlTokenType.Identifier: sb.Append('[').Append(token.Text).Append(']'); return;
        case TSqlTokenType.QuotedIdentifier: sb.Append(token.Text); return;
        default: throw new ArgumentException("Error: expected TokenType of token should be TSqlTokenType.Identifier or TSqlTokenType.QuotedIdentifier");
    }
}

在试图让这个答案起作用后,我想出了这个。

于 2014-09-11T02:47:15.757 回答
8

您可以在查询后立即使用此 sql 脚本。它将返回最后执行的查询中使用的表列表:

   SELECT Field1, Field2 
   FROM Table t JOIN OtherTable ON t.id=OtherTable.t_id

  ;WITH vwQueryStats AS(
     SELECT 
      COALESCE(OBJECT_NAME(s2.objectid),'Ad-Hoc') AS ProcName
      ,execution_count
      ,s2.objectid
      ,(
         SELECT TOP 1 
            SUBSTRING(s2.TEXT,statement_start_offset / 2+1 
            ,( ( CASE WHEN statement_end_offset = -1
                THEN (LEN(CONVERT(NVARCHAR(MAX),s2.TEXT)) * 2)
                ELSE statement_end_offset END)- statement_start_offset) / 2+1)) AS sql_statement
            ,last_execution_time
         FROM sys.dm_exec_query_stats AS s1
         CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS s2
    )
    SELECT TOP 1 * 
    INTO #lastQueryStats
    FROM vwQueryStats x
    WHERE sql_statement NOT like 'WITH vwQueryStats AS%'
    ORDER BY last_execution_time DESC

    SELECT
    TABLE_NAME
    FROM #lastQueryStats, INFORMATION_SCHEMA.TABLES tab 
    WHERE CHARINDEX( tab.TABLE_NAME, sql_statement) > 0


    DROP TABLE #lastQueryStats 

我采用了从这篇文章中检索最后执行的查询的查询,并对其进行了一些修改以与您的示例相匹配。

输出将按照您的要求:

 Table
 OtherTable

然后,如果你想让它们用逗号分隔,你可以这样做:

DECLARE @tableNames VARCHAR(MAX) 

SELECT @tableNames = COALESCE(@tableNames + ', ', '') + TABLE_NAME
FROM   #lastQueryStats, INFORMATION_SCHEMA.TABLES tab 
WHERE  CHARINDEX( tab.TABLE_NAME, sql_statement) > 0

SELECT @tableNames 

但是,您应该注意,在同时执行数千个查询的“常规”生产或 QA 环境中,这可能不起作用,因为可以在您的第一个查询和从数据库统计信息中提取信息的查询之间执行另一个查询。

希望能帮助到你

于 2013-05-22T13:37:30.160 回答
4

下面的代码基于Trisped的答案,但经过修改以使用省略模式名称的完全限定表名,以及一些清理/优化:

public class Parser
{
    public static List<string> GetTableNamesFromQueryString(string query)
    {
        var output = new List<string>();
        var sb = new StringBuilder();
        var parser = new TSql120Parser(true);

        var fromTokenTypes = new[]
        {
            TSqlTokenType.From,
            TSqlTokenType.Join
        };

        var identifierTokenTypes = new[]
        {
            TSqlTokenType.Identifier,
            TSqlTokenType.QuotedIdentifier
        };

        using (System.IO.TextReader tReader = new System.IO.StringReader(query))
        {
            IList<ParseError> errors;
            var queryTokens = parser.GetTokenStream(tReader, out errors);
            if (errors.Any())
            {
                return errors
                    .Select(e => string.Format("Error: {0}; Line: {1}; Column: {2}; Offset: {3};  Message: {4};", e.Number, e.Line, e.Column, e.Offset, e.Message))
                    .ToList();
            }

            for (var i = 0; i < queryTokens.Count; i++)
            {
                if (fromTokenTypes.Contains(queryTokens[i].TokenType))
                {
                    for (var j = i + 1; j < queryTokens.Count; j++)
                    {
                        if (queryTokens[j].TokenType == TSqlTokenType.WhiteSpace)
                        {
                            continue;
                        }

                        if (identifierTokenTypes.Contains(queryTokens[j].TokenType))
                        {
                            sb.Clear();
                            GetQuotedIdentifier(queryTokens[j], sb);

                            while (j + 2 < queryTokens.Count 
                                && queryTokens[j + 1].TokenType == TSqlTokenType.Dot 
                                && (queryTokens[j + 2].TokenType == TSqlTokenType.Dot || identifierTokenTypes.Contains(queryTokens[j + 2].TokenType)))
                            {
                                sb.Append(queryTokens[j + 1].Text);

                                if (queryTokens[j + 2].TokenType == TSqlTokenType.Dot)
                                {
                                    if (queryTokens[j - 1].TokenType == TSqlTokenType.Dot) 
                                        GetQuotedIdentifier(queryTokens[j + 1], sb);

                                    j++;

                                }
                                else
                                {
                                    GetQuotedIdentifier(queryTokens[j + 2], sb);
                                    j += 2;
                                }
                            }

                            output.Add(sb.ToString());
                        }
                        break;
                    }
                }
            }

            return output.Distinct().OrderBy(tableName => tableName).ToList();
        }
    }

    private static void GetQuotedIdentifier(TSqlParserToken token, StringBuilder sb)
    {
        switch (token.TokenType)
        {
            case TSqlTokenType.Identifier: 
                sb.Append('[').Append(token.Text).Append(']'); 
                break;
            case TSqlTokenType.QuotedIdentifier:
            case TSqlTokenType.Dot: 
                sb.Append(token.Text); 
                break;

            default: throw new ArgumentException("Error: expected TokenType of token should be TSqlTokenType.Dot, TSqlTokenType.Identifier, or TSqlTokenType.QuotedIdentifier");
        }
    }
}
于 2015-04-08T23:31:43.533 回答
1

Trisped 的解决方案非常有效。我修改了一行以确保不区分大小写并修剪括号。

老的:output.Add(sb.ToString());

新的:output.Add(sb.ToString().ToLower().Trim(new char[]{'[', ']'}));

于 2014-10-19T05:18:43.350 回答
1

您可以实现此目的的一种骇人听闻的方法是显式命名查询中的字段并在它们前面加上表名,例如

SELECT Field1 As "OtherTable.Field1",
       Field2 As "Table.Field2"
FROM Table t JOIN OtherTable ON t.id=OtherTable.t_id

本质上,您是在查询结果中提供自己的元数据。查询返回后,查看列名并实施自定义逻辑来拆分表名。

于 2013-05-22T13:00:49.900 回答