【问题标题】:List of tables used in an SQL QuerySQL 查询中使用的表列表
【发布时间】:2013-05-22 12:54:28
【问题描述】:

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

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

我希望得到

Table, OtherTable

谢谢

【问题讨论】:

  • 你为什么要这个?
  • 任何特定的 RDBMS/SQL 方言?如果是 TSQL,你可能会看 TSql100Parser example of using it to identify parameters here
  • 除非有点复杂的正则表达式,否则我想不出有什么能做到这一点。我很想知道现在是否有一些 API 或查询。
  • 我第二个 ITBeginner 的问题...你为什么要这样做?
  • 嗯,逻辑是你必须找到所有出现在诸如FROM JOIN等关键字之后的单词并列出它们。

标签: c# sql


【解决方案1】:

使用 C# 的一个解决方案是导入 Microsoft.SqlServer.TransactSql.ScriptDom(我在 C:\Program Files (x86)\Microsoft SQL Server\120\SDK\Assemblies\Microsoft.SqlServer.TransactSql.ScriptDom.dll 找到了 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");
    }
}

我在尝试让this answer 工作后想出了这个。

【讨论】:

  • 不适用于省略模式名称的完全限定表名......需要稍微调整以使其适用于这种情况
  • 我发布了一个修改您的代码以支持省略模式名称方案的答案
【解决方案2】:

您可以在查询后立即使用此 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 

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

输出将按照您的要求:

 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 环境中,这可能不起作用,因为在您的第一个查询和从数据库统计信息中提取信息的查询之间可能会执行另一个查询。

希望对你有帮助

【讨论】:

  • 这看起来很有趣,但是它不起作用。好吧,更糟糕的是,它只在某些时候有效。我的意思是重要的公园,显然逗号分隔不是问题。我确定我是唯一一个对数据库进行查询的人(它是本地数据库),但大多数时候我只收到空响应。
  • 你是在同一个会话中同时执行它们吗?
  • 是的,我是。我有一个包含〜数百条记录的数据库,当我调用 SELECT TOP 5 * FROM Result r JOIN Sample s ON r.sample_id=s.sample_id 时它总是有效,而当我调用 SELECT * FROM Result r JOIN Sample s ON 时它几乎不会r.sample_id=s.sample_id
  • @user436730 你是对的,抱歉我修好了。问题是在这种情况下 sql_statement NOT like 'SELECT TOP 1 * FROM(SELECT %' 我改为 WITH vwQueryStats AS% 我修改了原始 sn-p 忘记更改上次执行查询的条件。我试过了,现在可以工作了
【解决方案3】:

以下代码基于 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");
        }
    }
}

【讨论】:

  • 这个代码块是用来处理什么情况的? if (queryTokens[j + 2].TokenType == TSqlTokenType.Dot) { if (queryTokens[j - 1].TokenType == TSqlTokenType.Dot) GetQuotedIdentifier(queryTokens[j + 1], sb); j++;我正在为此编写单元测试,但找不到进入此块的方法。
  • @MatthewVines 已经 2 年了,老实说我不记得了,但我想到的是具有完全限定名称和模式的标记,例如“select * from [mydb].[myschema]。 [mytable]"
  • 感谢您的关注。在经历了很多不同的场景之后,我能够将这组表达式剥离为 if (queryTokens[j + 2].TokenType == TSqlTokenType.Dot) { j++; } 没有任何功能损失。
【解决方案4】:

实现此目的的一种hackish方法是显式命名查询中的字段并在它们前面加上表名,例如

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

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

【讨论】:

    【解决方案5】:

    Trisped 的解决方案完美运行。我修改了一行以确保不区分大小写并去掉括号。

    旧:output.Add(sb.ToString());

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 1970-01-01
      • 2018-12-19
      • 1970-01-01
      • 2011-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多