Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting table schema from a query

As per MSDN, SqlDataReader.GetSchemaTable returns column metadata for the query executed. I am wondering is there a similar method that will give table metadata for the given query? I mean what tables are involved and what aliases it has got.

In my application, I get the query and I need to append the where clause programically. Using GetSchemaTable(), I can get the column metadata and the table it belongs to. But even though table has aliases, it still return the real table name. Is there a way to get the aliase name for that table?

Following code shows getting the column metadata.

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();
}

This will give me details of columns correctly. But when I see BaseTableName for column Id, it is giving contact rather than the alias name c. Is there any way to get the table schema and aliases from a query like the above?

Any help would be great!

Edit

While I could use the execution plan suggested by Rob, I'd appreciate any alternative simple approaches.

Answering questions by tomekszpakowicz

Are you (or your application) source of the query in question? In that case you should know the aliases.

I am not the author of queries. We have a system where users can enter the query. We build columns out of it using the method I explained above. These details will be persisted and another user can use this like adding new criteria etc. So we need to build the SQL dynamically from the information we have. So when a column is aliased and we are not getting alias name, then the where clause constructed will be invalid.

Thanks

like image 371
Navaneeth K N Avatar asked Jun 17 '10 05:06

Navaneeth K N


Video Answer


3 Answers

Short answer

This won't work. You cannot, by design, get table aliases from result schema. And you cannot rely on being able to get them from query execution plan.

Long answer

When you get result of a SQL query, the query has already been parsed, validated, optimized, compiled into some internal representation and executed. Aliases are part of query's "source code" and are usually lost somewhere around step 1 and 2.

After query is executed the only things that can be seen as tables are a) real physical tables and b) returned data seen as single anonymous table. Everything between can be transformed or completely optimized out.

If DBMSes were required to retain aliases it would be practically impossible to optimize complex queries.

Possible solutions

I suggest restating a problem:

  1. Are you (or your application) source of the query in question? In that case you should know the aliases.

  2. If you get queries provided by someone else... Well... That depends on why are you adding where causes.

    • In the worst case, you'll have to parse queries yourself.

    • In the best case, you could give them access to views instead of real tables and put where clauses in the views.


Simple and ugly solution

If I understand your requirements correctly:

  • User A enters query into your program.

  • User B can run it (but cannot edit it) and sees returned data. Additionally she can add filters based on returned columns using some kind of widget provided by you.

  • You don't want to apply filter inside application but instead add them to the query, in order to avoid fetching unnecessary data from database.

In that case:

  • When A edits query try to run it and gather metadata for returned columns. If ColumnNames are not unique, complain to the author. Store metadata with query.

  • When B adds filter (based on query metadata), store both columns names and conditions.

  • At execution:

    • Check if filter columns are still valid (A might have changed query). If not remove invalid filters and/or inform B.

    • Execute query as something like:

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

If you want to gracefully handle database schema changes you need to add some additional checks to make sure metadata is consistent with what query really returns.

Security note

Your program is going to pass a query written by user A straight to database. It is crucial that you do it using database connection with permissions which do not exceed A's database permissions. Otherwise you are asking for SQL injection based exploits.

Corollary

If user A doesn't have direct access to the database out of security reasons, you cannot use above solution.

In that case the only way to make it safe is to make sure your application understands 100% of the query which means parsing it in your program and allowing only operations you consider safe.

like image 113
Tomek Szpakowicz Avatar answered Sep 19 '22 21:09

Tomek Szpakowicz


You could get the execution plan for the query, and then analyse the XML that's returned. This is like using the "Show Estimated Plan" option in Management Studio.

like image 42
Rob Farley Avatar answered Sep 21 '22 21:09

Rob Farley


It's almost like you need a parser to parse the SQL and then from the parsed query make a symbol table of aliases and the tables they refer to. Then combine that with the results of GetSchemaTable() so that you can map columns to the appropriate alias.

Anyway see the question Parsing SQL code in C# for some parsers. I haven't looked at them in detail but maybe one of them is what you need. If you are only doing select statements check out the ANTLR link and the grammar for http://www.antlr.org/grammar/1062280680642/MS_SQL_SELECT.html.

If your queries are simple, you could probably use regular expressions or your own custom grammar to parse out the aliases and table names from the query. This would probably be the easiest solution.

The most robust solution is probably to pay for someone else's parser that handles full SQL and breaks it up into a parse tree or something else where you can query it. I'm not sure the merits of each one and the price/robustness ratio. But some of them are ultra expensive.... I would say if you can't do it yourself explore the ANTLR grammar (because it is free) assuming you just need select statements. Otherwise you may have to pay....

Actually assuming your users aren't crazy SQL geniuses and using subqueries/etc. I don't see why you can't use the table names from the schema view that you said you got to find them in the query and then find the alias as either tablename alias or tablename as alias. That might work for many of the cases.... But for the full general case you'd need a full parser.....

like image 21
Cervo Avatar answered Sep 18 '22 21:09

Cervo