43

What's the best way to check if a table exists in a Sql database in a database independant way?

I came up with:

   bool exists;
   const string sqlStatement = @"SELECT COUNT(*) FROM my_table";

   try
    {
       using (OdbcCommand cmd = new OdbcCommand(sqlStatement, myOdbcConnection))
       {
            cmd.ExecuteScalar();
            exists = true;
       }
    }
    catch
    {
        exists = false;
    }

Is there a better way to do this? This method will not work when the connection to the database fails. I've found ways for Sybase, SQL server, Oracle but nothing that works for all databases.

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Carra
  • 16,707
  • 5
  • 58
  • 73

9 Answers9

67
bool exists;

try
{
    // ANSI SQL way.  Works in PostgreSQL, MSSQL, MySQL.  
    var cmd = new OdbcCommand(
      "select case when exists((select * from information_schema.tables where table_name = '" + tableName + "')) then 1 else 0 end");

    exists = (int)cmd.ExecuteScalar() == 1;
}
catch
{
    try
    {
        // Other RDBMS.  Graceful degradation
        exists = true;
        var cmdOthers = new OdbcCommand("select 1 from " + tableName + " where 1 = 0");
        cmdOthers.ExecuteNonQuery();
    }
    catch
    {
        exists = false;
    }
}
Matt Mitchell
  • 37,997
  • 35
  • 111
  • 181
Michael Buen
  • 36,153
  • 6
  • 84
  • 113
  • See this link http://msdn.microsoft.com/en-us/library/ms186778.aspx for more information on information_schema in Sql-Server. – GvS Jan 21 '09 at 10:25
  • 1
    -1 from me. This doesn't work in MySql. Coz, it returns true if any database has a table named 'tableName'. I have tested this with MySql5.1 + Navicat8. – user366312 Apr 30 '10 at 17:41
  • @JMSA: sorry, I forgot to include the schema_name(the database name field). `select * from information_schema.tables where schema_name = 'yourDatabaseNameHere' and table_name = 'yourTableNameHere`. kindly undo the downvote – Michael Buen May 01 '10 at 00:26
  • This wouldn't work either. Coz, Navicat is showing that, there is no field named 'schema_name' in the 'information_schema.tables'-table. There is a field named 'table_schema' and there is null inserted in every row in that field. – user366312 May 02 '10 at 08:52
  • 11
    @JMSA: would that be my fault if a certain database is not ANSI SQL-compliant? perhaps you should contribute solution, and not abruptly downvoting just because my answer doesn't cover all database – Michael Buen May 02 '10 at 11:03
  • it's not schema_name, it's table_schema, i should have copy pasted what i tested on my Mysql installation here. this should be working in your MySQL commandline or programatically, tried the query below here, it's working, otherwise it could be Navicat problem: `select table_schema, table_name from information_schema.tables where table_schema = 'database_name_here' and table_name = 'table_name_here';` – Michael Buen May 02 '10 at 11:26
  • I received an error ``ExecuteScalar: Connection property has not been initialized`` because I forgot to assign the connection: ``cmd.Connection = mDbConnection;``. Could be done in the constructor of ``OdbcCommand`` as well. – Peopleware Jun 25 '15 at 11:15
11

If you're trying for database independence you will have to assume a minimum standard. IIRC The ANSI INFORMATION_SCHEMA views are required for ODBC conformance, so you could query against them like:

select count (*) 
  from information_schema.tables 
 where table_name = 'foobar'

Given that you are using ODBC, you can also use various ODBC API calls to retrieve this metadata as well.

Bear in mind that portability equates to write-once test anywhere so you are still going to have to test the application on every platform you intend to support. This means that you are inherently limited to a finite number of possible database platforms as you only have so much resource for testing.

The upshot is that you need to find a lowest common denominator for your application (which is quite a lot harder than it looks for SQL) or build a platform-dependent section where the non-portable functions can be plugged in on a per-platform basis.

ConcernedOfTunbridgeWells
  • 59,622
  • 15
  • 138
  • 193
  • 1
    Then, if i understand your post correctly, every DBMS should have an INFORMATION_SCHEMA view according to some standard ? – Frederik Gheysels Jan 21 '09 at 19:38
  • I believe that the INFORMATION_SCHEMA views are required for compliance with the ANSI SQL-92 standard. However, DBMS vendors tend to play a bit fast and loose with the ANSI SQL standards in their claims of compliance. – ConcernedOfTunbridgeWells Jan 25 '09 at 17:29
10

I don't think that there exists one generic way that works for all Databases, since this is something very specific that depends on how the DB is built.

But, why do you want to do this using a specific query ? Can't you abstract the implementation away from what you want to do ? I mean: why not create a generic interface, which has among others, a method called 'TableExists( string tablename )' for instance. Then, for each DBMS that you want to support , you create a class which implements this interface, and in the TableExists method, you write specific logic for this DBMS.
The SQLServer implementation will then contain a query which queries sysobjects.

In your application, you can have a factory class which creates the correct implementation for a given context, and then you just call the TableExists method.

For instance:

IMyInterface foo = MyFactory.CreateMyInterface (SupportedDbms.SqlServer);

if( foo.TableExists ("mytable") )
...

I think this is how I should do it.

Frederik Gheysels
  • 53,692
  • 9
  • 95
  • 146
4

I fully support Frederik Gheysels answer. If you have to support multiple database systems, you should implement your code against an abstract interface with specific implementations per database system. There are many more examples of incompatible syntax than just checking for an existing table (e.g.: limiting the query to a certain number of rows).

But if you really have to perform the check using the exception handling from your example, you should use the following query that is more efficient than a COUNT(*) because the database has no actual selection work to do:

SELECT 1 FROM my_table WHERE 1=2
Sebastian Dietz
  • 5,382
  • 1
  • 28
  • 38
4

I would avoid executing the select count(x) from xxxxxx as the DBMS will actually go ahead and do it which may take some time for a large table.

Instead just prepare a select * from mysterytable query. The prepare will fail if mysterytable does not exist. There is no need to actually execute the prepared statement.

Imran Ali Khan
  • 7,052
  • 15
  • 47
  • 74
James Anderson
  • 26,221
  • 7
  • 45
  • 76
  • I don't know about ODBC, but on Oracle using JDBC you can prepare a statement that will totally fail when you run it. – Mr. Shiny and New 安宇 Jan 21 '09 at 14:40
  • Yes-- but he only wants to chjeck if the table exists which prepare will validate. Especially if its the simple "Select * from ?????" which can only fail if ??? does not exist. – James Anderson Jan 21 '09 at 15:45
  • I really doubt "select count(*)" (with no where clause) will take a long time for a large table - I'd imagine all databases store rowcounts in internal databases/indexes/caches, so it'd just be a single lookup. The size of the table would therefore have no relevance to the time the query takes to run. – David_001 Jun 11 '10 at 07:09
  • @David -- depends on the DBMS but most do not keep accurate row counts and will actually count the rows (or at least the entries in the PK index). If you think of it the tradeoff between maintaining a "count" which would be a point of contention for every insert or delete vs. optimising a comapritivly rare select statement is a good one. – James Anderson Jun 14 '10 at 05:51
2

The following works well for me...

private bool TableExists(SqlConnection conn, string database, string name)
{
    string strCmd = null;
    SqlCommand sqlCmd = null;

    try
    {
        strCmd = "select case when exists((select '['+SCHEMA_NAME(schema_id)+'].['+name+']' As name FROM [" + database + "].sys.tables WHERE name = '" + name + "')) then 1 else 0 end";
        sqlCmd = new SqlCommand(strCmd, conn);

        return (int)sqlCmd.ExecuteScalar() == 1;
    }
    catch { return false; }
}
Arvo Bowen
  • 3,742
  • 6
  • 37
  • 78
2

In current project on my job I need to write 'data agent' which would support a lot of database types.

So I decided to do next: write a base class with the base (database independent) functionality using virtual methods and override in subclasses all database-specific moments

abatishchev
  • 92,232
  • 78
  • 284
  • 421
0

If you want to avoid try-catch solutions, I'm suggesting this method, using sys.tables

private bool IsTableExisting(string table)
    {
        string command = $"select * from sys.tables";
        using (SqlConnection con = new SqlConnection(Constr))
        using (SqlCommand com = new SqlCommand(command, con))
        {
            SqlDataReader reader = com.ExecuteReader();
            while (reader.Read())
            {
                if (reader.GetString(0).ToLower() == table.ToLower())
                    return true;
            }
            reader.Close();
        }
        return false;
    }
  • You shouldn't be assuming the server has case-insensitive object names (comparing lowercase form), nor should you force the server to return a list of *every single table*. In SQL Server, just check if `OBJECT_ID(@tableName)` returns NULL (and use parameterization, avoiding string concatenation when building queries!). Please consider security! – Elaskanator Oct 15 '18 at 17:05
0

Very Simple

use YOUR_DATABASE --OPTIONAL
SELECT count(*) as Exist from INFORMATION_SCHEMA.TABLES where table_name = 'YOUR_TABLE_NAME'

If the answer is 1, There is a table. If the answer is 0, There is no table.