【问题标题】:Getting table schema from a query从查询中获取表模式
【发布时间】:2010-06-17 05:36:19
【问题描述】:

根据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 子句将是无效的。

谢谢

【问题讨论】:

  • 你为什么要这样做?为什么要将 where 子句附加到此查询?
  • 另一种简单的方法?使用 BaseTableName,而不是别名。真实的表名很好用,为什么你认为你需要别名?

标签: .net sql ado.net metadata


【解决方案1】:

简答

这行不通。根据设计,您不能从结果模式中获取表别名。而且您不能依赖能够从查询执行计划中获取它们。

长答案

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

查询执行后,唯一可以被视为表的内容是 a) 真实的物理表和 b) 被视为单个匿名表的返回数据。两者之间的一切都可以转换或完全优化。

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

可能的解决方案

我建议重述一个问题:

  1. 您(或您的应用程序)是相关查询的来源吗?在这种情况下,您应该知道别名。

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

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

    • 在最好的情况下,您可以让他们访问视图而不是真实表,并将 where 子句放在视图中。


简单而丑陋的解决方案

如果我正确理解您的要求:

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

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

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

在这种情况下:

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

  • 当 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% 的查询,这意味着在您的程序中解析它并只允许您认为安全的操作。

【讨论】:

  • 谢谢。我已编辑我的问题以回答您提出的问题。
  • 再次感谢。以前是这样实现的。但是当我们将用户 A 输入的主查询包装为子查询时,会导致性能问题。因为数据库引擎必须先执行内部查询,它没有任何条件,可能会导致全表扫描。无论如何感谢您的所有建议。我很感激。
  • 你真的见过那些性能问题吗?您是否手动运行了两个版本(包装并添加了 where)并对其进行计时?你比较过执行计划吗?最后但并非最不重要的一点是,在什么 RDBMS 上?
  • 首先检查这个假设。衡量绩效并比较执行计划。另请记住,SQL Server 根据查询的确切文本缓存查询计划。如果您多次执行相同的查询,这一点很重要。如果您在这里写下测试结果以及您最终决定如何解决问题,那就太好了。
  • 这就是我所期望的。对于通用查询,体面的 RDBMS 应该比您的应用程序更好地加快查询速度。
【解决方案2】:

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

【讨论】:

  • 谢谢。但是,执行计划不会给我表别名。
  • 当然可以。我刚刚运行了SELECT * FROM Production.Product p,并在 XML 中得到了这个:<Object Database="[AdventureWorks]" Schema="[Production]" Table="[Product]" Index="[PK_Product_ProductID]" Alias="[p]" IndexKind="Clustered" /></IndexScan>
  • 太棒了!你是如何获得 XML 的?你能显示一些小代码吗?
  • 使用set showplan_xml on,但请记住,这样做会停止查询运行。所以你会想在没有这个选项的情况下再次运行它。
  • 我没有得到索引计划,但我确实得到了 ColumnReference 条目,只要您从表中选择一列就足以创建别名表映射...跨度>
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 是的。如果需求变得更复杂,那么 Appu 所描述的唯一好的解决方案是将查询解析为应用程序中的表达式树。然后,您可以根据需要编辑树,添加其他条件、子查询、连接其他表等。您还可以准确控制可以使用哪些命令。但这很难,我不知道任何通用图书馆。我考虑过指向 Microsoft Entity Framework 中的解析器(在您的第一个链接中回答),但我自己从未使用过它,所以我不知道它是否适用于这种情况。
【解决方案4】:

我认为 Rob Farley 的 showplan xml 会为您工作(假设您运行的 SQL Server 足够晚且具有此功能)。

对于每个选定的列,每一列似乎都有<ColumnReference Server="" Database="" Schema="" Table="" Alias="" Column=""/>。假设每个表至少有一个列,那么在别名和表之间进行映射应该很简单。

【讨论】:

  • 对我来说,当它发布的接口不够用时,似乎使用 pdb 文件中的调试数据(符号、局部变量信息等)来窥视 dll。当你调试一些问题时,这可以给你很多。但如果你在真实代码中依赖这些信息,它迟早会失败。
【解决方案5】:

其实可以的。在这里查看我的答案:https://stackoverflow.com/a/19537164/88409

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

更进一步,我不得不推测无法优化此类别名,因为引擎必须使用它们来区分同一表中同一列的不同实例。

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

"FROM 子句中的对象 "Lessons" 和 "Lessons" 具有相同的 暴露的名字。 使用相关名称来区分它们。"

例如,如果您运行类似 'set showplan_xml on; select * from Lessons a, Lessons b, Lessons c, (select * from Lessons 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>

【讨论】:

  • 只是为了补充......这只是到目前为止。能够区分同名的列不能超过一个级别,因为这样列名就会变得不明确。例如,虽然您可以运行 select * from (select * from Lessons a) a, (select * from Lessons b) b,但您不能将整个内容放在括号中并从中选择 * 作为(子查询)c,因为查询验证将失败,例如 The column 'ID' was specified multiple times for 'c'.
猜你喜欢
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-30
  • 1970-01-01
  • 2018-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多