检查SQL表是否存在。

47

如何以数据库无关的方式检查 Sql 数据库中是否存在表?

我想到的方法是:

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

有没有更好的方法来做这件事?当与数据库的连接失败时,这种方法将无法工作。我已经找到了Sybase、SQL Server、Oracle的解决方案,但没有适用于所有数据库的方法。


3
更好的方法是使用 "SELECT 1 FROM tbl WHERE 1=0",这样不会消耗太多资源。 - Alex Shnayder
9个回答

69
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;
    }
}

1
从我这里返回-1。这在MySql中不起作用。因为如果任何数据库都有一个名为'tableName'的表,则它会返回true。我已经在MySql5.1 + Navicat8上进行了测试。 - user366312
@JMSA:抱歉,我忘记包括schema_name(数据库名称字段)了。select * from information_schema.tables where schema_name = 'yourDatabaseNameHere' and table_name = 'yourTableNameHere。请撤销您的反对票。 - Michael Buen
这也行不通。因为Navicat显示,在“information_schema.tables”表中没有名为“schema_name”的字段。有一个名为“table_schema”的字段,并且在该字段的每一行中都插入了null。 - user366312
12
如果某个数据库不符合 ANSI SQL 标准,那是否是我的错呢?也许你应该提供解决方案,而不是因为我的答案没有涵盖所有数据库就突然给我投反对票。 - Michael Buen
我收到了一个错误信息“ExecuteScalar: Connection property has not been initialized”,因为我忘记分配连接:“cmd.Connection = mDbConnection;”。这也可以在“OdbcCommand”的构造函数中完成。 - Peopleware
显示剩余2条评论

13

如果您想实现数据库独立性,您需要假定一个最低标准。我记得,为了符合ODBC规范,必须使用ANSI INFORMATION_SCHEMA视图,所以您可以像这样对它们进行查询:

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

考虑到您正在使用ODBC,您也可以使用各种ODBC API调用来检索此元数据。

请记住,可移植性等同于一次编写,到处测试,因此您仍然需要在打算支持的每个平台上测试应用程序。这意味着,由于您只有有限的资源进行测试,您的可能数据库平台数量是有限的。

结果是,您需要为应用程序找到最低公共分母(对于SQL而言,这比看起来要困难得多),或者构建一个平台相关部分,在其中可以根据每个平台插入不可移植函数。


1
那么,如果我正确理解了您的帖子,每个DBMS都应该根据某些标准拥有一个INFORMATION_SCHEMA视图? - Frederik Gheysels
我认为使用INFORMATION_SCHEMA视图是符合ANSI SQL-92标准所必需的。然而,数据库管理系统供应商在声称符合标准时往往会玩得有些放松。 - ConcernedOfTunbridgeWells
这正是我一直在寻找的。直截了当,简单明了地检查表是否存在。一旦确定表不存在,创建表的代码同样简单易读。 - undefined

10

我不认为存在一种适用于所有数据库的通用方法,因为这是非常特定的,取决于数据库的构建方式。

但是,你为什么要使用特定查询来做这件事呢? 你不能将实现与所需操作抽象出来吗? 我的意思是:为什么不创建一个通用接口,其中包括一个名为"TableExists(string tablename)"的方法。 然后,对于每个你想要支持的DBMS,你都创建一个实现此接口的类,并在TableExists方法中编写特定于此DBMS的逻辑。
例如,SQLServer的实现将包含查询sysobjects的查询语句。

在你的应用程序中,你可以拥有一个工厂类,它为给定的上下文创建正确的实现,然后你只需要调用TableExists方法。

例如:

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

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

我认为这就是我应该做的方式。


1
这就是我们在主应用程序中的做法。但是,如果您只有一个ODBC连接,却不知道它背后的数据库是什么呢? - Carra
我的经验告诉我,这是错误的方式!不幸的是,你可能会得到零性能。 - abatishchev
@abtischev - 你能详细说明一下吗? - MPritchard

5
我完全支持Frederik Gheysels的回答。如果您需要支持多个数据库系统,则应该使用具有特定实现的抽象接口来实现您的代码。除了检查是否存在表格(例如:将查询限制为某些行数),还有许多不兼容语法的例子。
但是,如果您确实需要使用您示例中的异常处理执行检查,则应该使用以下查询,该查询比COUNT(*)更有效,因为数据库没有实际选择工作要做:
SELECT 1 FROM my_table WHERE 1=2

4
我建议避免执行 select count(x) from xxxxxx,因为DBMS实际上会执行该查询,对于大表可能需要花费一些时间。
相反,只需准备一个 select * from mysterytable 查询。如果mysterytable不存在,则准备将失败。无需实际执行准备好的语句。

我不了解ODBC,但是在使用JDBC的Oracle中,您可以准备一条语句,在运行时完全失败。 - Mr. Shiny and New 安宇
是的 - 但他只想检查表是否存在,而 prepare 将验证。特别是如果它是简单的 "Select * from ?????",只有当 ??? 不存在时才会失败。 - James Anderson
我真的怀疑在没有where子句的情况下,"select count(*)"会花费很长时间来对一个大表进行计数 - 我想所有数据库都会将行数存储在内部数据库/索引/缓存中,因此只需进行单次查找。因此,表的大小与查询运行所需的时间无关。 - David_001
@David -- 这取决于DBMS,但大多数不会保持准确的行计数,并且实际上会计算行数(或至少是PK索引中的条目)。如果你考虑到这一点,维护一个“计数”将成为每次插入或删除的争议点,而优化相对较少的选择语句则是一个好的选择。 - James Anderson

3
以下对我很有效...
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; }
}

2
在我目前的工作项目中,我需要编写一个“数据代理”,以支持许多数据库类型。因此,我决定采取以下步骤:编写一个基类,具有基本(与数据库无关)功能,使用虚拟方法,并在子类中覆盖所有特定于数据库的内容。

肯定是这样做的,没错。 - Nyerguds

2
非常简单。
use YOUR_DATABASE --OPTIONAL
SELECT count(*) as Exist from INFORMATION_SCHEMA.TABLES where table_name = 'YOUR_TABLE_NAME'

如果答案是1,那么就有一张表格。 如果答案是0,那么就没有表格。

0

如果你想避免使用 try-catch 解决方案,我建议使用这种方法,使用 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;
    }

您不应该假定服务器具有不区分大小写的对象名称(比较小写形式),也不应该强制服务器返回每个表的列表。在SQL Server中,只需检查OBJECT_ID(@tableName)是否返回NULL(并在构建查询时使用参数化,避免字符串连接!)。请考虑安全性! - Elaskanator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接