使用PHP/PDO检查数据库表是否存在

31

我希望检查在使用PHP和PDO连接的数据库中是否存在一个特定名称的表格。

它必须适用于所有数据库后端,如MySQL、SQLite等。

15个回答

46

以下是检查表是否存在的完整函数。

/**
 * Check if a table exists in the current database.
 *
 * @param PDO $pdo PDO instance connected to a database.
 * @param string $table Table to search for.
 * @return bool TRUE if table exists, FALSE if no table found.
 */
function tableExists($pdo, $table) {

    // Try a select statement against the table
    // Run it in try-catch in case PDO is in ERRMODE_EXCEPTION.
    try {
        $result = $pdo->query("SELECT 1 FROM {$table} LIMIT 1");
    } catch (Exception $e) {
        // We got an exception (table not found)
        return FALSE;
    }

    // Result is either boolean FALSE (no table found) or PDOStatement Object (table found)
    return $result !== FALSE;
}

注意:PDO仅在被告知时抛出异常,默认情况下它是静默的并不会抛出任何异常。这就是为什么我们需要检查结果。请参阅php.net上的PDO错误处理

2
这并不能防止 SQL 注入。 - livefree75
6
或许您可以更具体些吗?我在这里没有看到客户提供的字段,也许我不确定您在他/她的代码中看到了什么问题? - Steve
4
为什么需要保护?如果它在数据库层中,保护措施不应该放在那里。保护和安全并不意味着在任何地方都插入过滤/清洗函数。 - Ran Bar-Zik
3
如果想要进行过滤操作,在函数开头加入以下代码:$table = preg_replace('/[^\da-z_]/i', '', $table);。该代码可以对 $table 变量进行过滤,并保留其中的数字、大小写字母和下划线字符,其他字符将被移除。 - rybo111

14

在我继续之前,我确实意识到这是一种针对MySQL的特定解决方案。

虽然这里提到的所有解决方案都可能有效,但我(个人而言)喜欢让PDO不抛出异常(只是个人喜好)。

因此,我使用以下内容来测试表创建:

SHOW TABLES LIKE 'some_table_of_mine';

如果表不存在,不会生成错误状态,而是只会得到一个零结果集。对我来说,工作速度快且一致。


10

应该做:

select 1 from your_table

然后捕获错误。如果没有出现任何错误,但结果集中只包含一个值为“1”的列,则该表存在。


3
如果你有很多行的话,那可能会很危险。 - feihtthief
7
有办法绕过这个问题。例如,您可以仅获取第一行。或者更好的方法是,甚至不要执行语句,只需准备它。 - Milan Babuškov
@MilanBabuškov 我不明白准备语句而不执行如何告诉我们表是否存在。我有什么遗漏吗? - SalientGreen
这个答案并没有涉及到使用PDO或PHP,其中前者在某些情况下存在关于异常处理的微妙问题。 - David Timothy Strauss
1
如果表为空,则此操作将失败。 - Yuri

4

一旦你通过PDO获取了数据库句柄,你可以这样做:

$tableExists = gettype($dbh->exec("SELECT count(*) FROM $table")) == 'integer';

或者将其包装在一个函数中。

起初我尝试用try/catch来解决问题,但即使表不存在,也没有异常。最后我检查了dbh exec调用返回值的数据类型。如果在select count上有匹配(即使计数为0),它是一个整数,否则就是false的布尔值,表示没有结果。

我认为这对PDO支持的所有数据库类型都适用,因为语法非常简单。


2
如果我的表有一百万行会发生什么? - Stephan
如果表使用MyISAM引擎,这个操作非常快,因为值被存储为元数据。(http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count) - Purefan
MySQL已经很久远离MyISAM,而且它远不是唯一一个使计算行代价昂贵的数据库。这也无法处理在异常模式下的PDO。 - David Timothy Strauss

3
作为项目的一部分,创建一个模式视图。
对于Oracle来说,它可能是这样的:
SELECT TABLE_NAME FROM ALL_TABLES

对于Mysql:

SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = 'mydbname'

然后在您的代码中对该视图运行查询。


问题是关于PDO的。请相应地更新答案。 - sitilge
2
@sitilge PDO与手头的问题正交。它只是运行查询的工具。这是一个可以接受的答案。 - Félix Adriyel Gagnon-Grenier
@FélixGagnon-Grenier,确实,这是一个工具。然而,很可能,OP希望得到一个“这就是你如何使用PDO”的答案,不是吗? - sitilge
1
@sitilge 嗯,也许吧。我并不是非常强烈地反对,只是因为“这就是你使用PDO的方法”在成千上万的教程中都有解释:它被称为运行SQL查询。如果有人不能理解SQL代码实际上是要通过PDO或其他方式运行的,那么他们也不太可能知道如何使用详细的答案... - Félix Adriyel Gagnon-Grenier
“这就是如何使用PDO”的教程在成千上万的教程中都有解释:它被称为运行SQL查询。但是,如果要正确地使用PDO,则需要从PDOException中捕获与缺少表相关的精确错误代码,这与运行预期成功的常规查询不同。如果您捕获所有异常(甚至是PDOException),则可以检测到“缺少表”,例如,如果数据库连接已断开。” - David Timothy Strauss
显示剩余3条评论

1

起初,我使用了被接受的答案,但后来发现在空表格时会失败。这是我现在正在使用的代码:

function DB_table_exists($db, $table){
    GLOBAL $db;
    try{
        $db->query("SELECT 1 FROM $db.$table");
    } catch (PDOException $e){
        return false;
    }
    return true;
}

这段代码是我为PDO编写的扩展类的一部分。如果表不存在,它会产生一个错误(并返回false),但如果表存在和/或为空,则会成功执行。

1
一个简单的PDO两行代码,适用于MySQL(不确定其他数据库):
$q = $pdo->query("SHOW TABLES LIKE '{$table}'");
$tableExists = $q->fetchColumn();

1
您可以通过使用类似于“SHOW TABLES LIKE 'your_table'”的查询并计算行数来避免依赖错误。我已经成功地在MySQL和PDO中使用了这种方法,但还没有在其他数据库中进行测试。

的确,这种方法在其他数据库中不起作用(尽管其他数据库通常有MySQL信息模式和 SHOW TABLES 的等价物)。 - David Timothy Strauss
@DavidTimothyStrauss,您似乎对改进这个问题及其答案很感兴趣,这是非常好的,但请注意您的编辑建议。您正在更改其他人的内容,甚至在某些时候直接重写答案。如果您认为答案是错误的,正确的做法是将其投票否决,并发表评论以便原作者进行修正,而不是按照您自己的方式进行修正。谢谢! - Félix Adriyel Gagnon-Grenier

1
如果您在同一语句中有其他重要操作,可以使用e->errorInfo。
try{                
    //Your major statements here
}
catch(PDOException $e){
    if($e->errorInfo[1] == 1146){
        //when table doesn't exist
    }      
}

1
由于权限问题,您可能想在生产中使用 $e->getCode() - leopold

0

这个完整的函数与esbite的答案非常相似,但包括保护免受SQL注入攻击的代码。此外,当涉及的表为空时,您可能无法从接受的答案中获得一致的结果。

/**
 * This function checks if the table exists in the passed PDO database connection
 * @param PDO $pdo - connection to PDO database table
 * @param type $tableName 
 * @return boolean - true if table was found, false if not
 */
function tableExists(PDO $pdo, $tableName) {
    $mrSql = "SHOW TABLES LIKE :table_name";
    $mrStmt = $pdo->prepare($mrSql);
    //protect from injection attacks
    $mrStmt->bindParam(":table_name", $tableName, PDO::PARAM_STR);

    $sqlResult = $mrStmt->execute();
    if ($sqlResult) {
        $row = $mrStmt->fetch(PDO::FETCH_NUM);
        if ($row[0]) {
            //table was found
            return true;
        } else {
            //table was not found
            return false;
        }
    } else {
        //some PDO error occurred
        echo("Could not check if table exists, Error: ".var_export($pdo->errorInfo(), true));
        return false;
    }
}

1
这只适用于MySQL。检查表的存在性不应该需要防止SQL注入,因为表的检查应该仅使用程序员指定的值或已知安全生成的名称进行。如果代码允许非特权用户创建具有任意名称的表,则已经存在更大的问题。 - David Timothy Strauss

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