从查询中获取表模式

时间:2010-06-17 05:36:20

标签: .net sql ado.net metadata

根据MSDNSqlDataReader.GetSchemaTable返回已执行查询的列元数据。我想知道是否有一个类似的方法将给出给定查询的表元数据?我的意思是涉及哪些表以及它所拥有的别名。

在我的应用程序中,我得到了查询,我需要以编程方式附加where子句。使用GetSchemaTable(),我可以获取列元数据及其所属的表。但即使表有别名,它仍然返回真正的表名。有没有办法获得该表的别名?

以下代码显示获取列元数据。

const string connectionString = "your_connection_string";
string sql = "select c.id as s,c.firstname from contact as c";

using(SqlConnection connection = new SqlConnection(connectionString))
using(SqlCommand command = new SqlCommand(sql, connection))
{
    connection.Open();
    SqlDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo);
    DataTable schema = reader.GetSchemaTable();
    foreach (DataRow row in schema.Rows)
    {
        foreach (DataColumn column in schema.Columns)
        {
            Console.WriteLine(column.ColumnName + " = " + row[column]);
        }
        Console.WriteLine("----------------------------------------");
    }
    Console.Read();
}

这将正确地提供列的详细信息。但是当我看到BaseTableNameId时,它会提供contact而不是别名c。有没有办法从上面的查询中获取表模式和别名?

任何帮助都会很棒!

修改

虽然我可以使用Rob建议的执行计划,但我会感谢任何其他简单的方法。

通过tomekszpakowicz回答问题

  

您(或您的申请)来源吗?   有问题的查询?在这种情况下   你应该知道别名。

我不是查询的作者。我们有一个用户可以输入查询的系统。我们使用上面解释的方法从中构建列。这些细节将被保留,另一个用户可以像添加新标准一样使用它。因此我们需要根据我们的信息动态构建SQL。因此,当列具有别名并且我们没有获得别名时,则构造的where子句将无效。

由于

5 个答案:

答案 0 :(得分:11)

简短回答

这不起作用。根据设计,您无法从结果模式中获取表别名。并且您不能依赖于能够从查询执行计划中获取它们。

答案很长

当您获得SQL查询的结果时,该查询已经被解析,验证,优化,编译成一些内部表示并执行。别名是查询的“源代码”的一部分,通常在步骤1和2的某处丢失。

执行查询后,唯一可以看作表的东西是a)真实的物理表和b)返回的数据看作单个匿名表。可以转换或完全优化其间的所有内容。

如果要求DBMS保留别名,那么优化复杂查询几乎是不可能的。

可能的解决方案

我建议重述一个问题:

  1. 您(或您的应用程序)是否有问题的来源?在这种情况下,您应该知道别名。

  2. 如果您收到其他人提供的查询......那么......这取决于您为何添加原因。

    • 在最糟糕的情况下,您必须自己解析查询。

    • 在最好的情况下,您可以授予他们访问视图而不是真实表格的权限,并在视图中放置where子句。


  3. 简单而难看的解决方案

    如果我理解你的要求:

    • 用户A在您的程序中输入查询。

    • 用户B可以运行它(但不能编辑它)并查看返回的数据。 此外,她可以使用您提供的某种小部件,根据返回的列添加过滤器。

    • 您不希望在应用程序中应用过滤器,而是将它们添加到查询中,以避免从数据库中获取不必要的数据。

    在那种情况下:

    • 当编辑查询时,尝试运行它并收集返回列的元数据。  如果ColumnName不是唯一的,请向作者投诉。  使用查询存储元数据。

    • 当B添加过滤器(基于查询元数据)时,存储两个列名称  和条件。

    • 执行时:

      • 检查过滤器列是否仍然有效(A可能已更改查询)。 如果没有删除无效过滤器和/或通知B.

      • 执行查询,如:

         select *
         from ({query entered by A}) x
         where x.Column1 op1 Value1
             and x.Column2 op2 Value2
        

    如果要优雅地处理数据库架构更改,则需要添加一些额外的检查,以确保元数据与实际返回的查询一致。

    安全提示

    您的程序将直接将用户A写入的查询传递给数据库。 使用不超过A的数据库权限的权限使用数据库连接来执行此操作至关重要。 否则,您要求基于SQL注入的漏洞利用。

    <强>推论

    如果出于安全原因,用户A无法直接访问数据库,则无法使用上述解决方案。

    在这种情况下,确保其安全的唯一方法是确保您的应用程序理解100%的查询,这意味着在程序中解析它并仅允许您认为安全的操作。

答案 1 :(得分:4)

您可以获取查询的执行计划,然后分析返回的XML。这就像使用Management Studio中的“显示估计计划”选项一样。

答案 2 :(得分:2)

这几乎就像你需要一个解析器来解析SQL,然后从解析的查询中创建一个别名的符号表和它们引用的表。然后将其与GetSchemaTable()的结果相结合,以便您可以将列映射到适当的别名。

无论如何,对于某些解析器,请查看问题Parsing SQL code in C#。我没有详细看过它们,但也许其中一个就是你需要的。如果您只是在执行select语句,请查看ANTLR链接和http://www.antlr.org/grammar/1062280680642/MS_SQL_SELECT.html的语法。

如果您的查询很简单,您可以使用正则表达式或您自己的自定义语法来解析查询中的别名和表名。这可能是最简单的解决方案。

最强大的解决方案可能是支付处理完整SQL的其他人的解析器,并将其分解为解析树或其他可以查询它的内容。我不确定每个的优点和价格/稳健性比率。但是其中一些是非常昂贵的...我想如果你不能自己探索ANTLR语法(因为它是免费的),假设你只需要select语句。否则你可能要付钱......

实际上假设您的用户不是疯狂的SQL天才并使用子查询/等。我不明白为什么你不能使用你在查询中找到它们的模式视图中的表名,然后将别名作为tablename别名或tablename作为别名。这可能适用于许多情况......但是对于完整的一般情况,你需要一个完整的解析器.....

答案 3 :(得分:0)

我认为Rob Farley的showplan xml对你有用(假设你运行的SQL Server已经足够晚了)。

每个列似乎都有<ColumnReference Server="" Database="" Schema="" Table="" Alias="" Column=""/>。假设每个表至少有一列,在别名和表之间进行映射应该是微不足道的。

答案 4 :(得分:0)

实际上,你可以。请在此处查看我的回答:https://stackoverflow.com/a/19537164/88409

您需要做的是使用set showplan_xml on运行所有静态查询一次,解析返回的XML,找到的第一个<OutputList>将是顶级输出列。只要在查询中首次引用查询中的表时为其分配别名,这些别名就会传递到输出列。

为了更进一步,我不得不推测这些别名不能被优化,因为引擎必须使用它们来区分同一个表中同一列的不同实例。

事实上,如果你运行这样的查询:select * from Lessons, Lessons,引擎基本上会告诉你这条消息:

  

“FROM子句中的对象”课程“和”课程“具有相同的内容   暴露的名字。 使用相关名称来区分它们。

例如,如果您运行'set showplan_xml on; 选择*来自课程a,课程b,课程c,(从课程d中选择*)subquery_aliases_wont_stick_like_table_aliases`

你会得到这样的输出:

<OutputList>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="ID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Name"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Description"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="Enabled"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[a]" Column="LessonTypeID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="ID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Name"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Description"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="Enabled"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[b]" Column="LessonTypeID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="ID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Name"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Description"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="Enabled"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[c]" Column="LessonTypeID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[d]" Column="ID"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[d]" Column="Name"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[d]" Column="Description"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[d]" Column="Enabled"/>
  <ColumnReference Database="[sensitive]" Schema="[dbo]" Table="[Lessons]" Alias="[d]" Column="LessonTypeID"/>
</OutputList>
相关问题