# 数据库查询

## 查询基础知识

备注

CodeIgniter 不支持在表名和列名中使用点号（`.`）。 自 v4.5.0 版本起，支持包含点号的数据库名。

### 常规查询

#### $db->query()

要执行查询，请使用 `query()` 方法：

    <?php

    $db = db_connect();
    $db->query('YOUR QUERY HERE');

当执行“读取”类型查询时，`query()` 方法会返回一个数据库结果 对象，你可以使用该对象来 显示结果。当执行“写入”类型查询时，该方法仅根据成功或失败返回 true 或 false。在检索数据时，你通常会将查询结果赋值给自己的变量，像这样：

    <?php

    $query = $db->query('YOUR QUERY HERE');

备注

如果你使用 OCI8 驱动程序，SQL 语句不应以分号（`;`）结尾。 PL/SQL 语句应以分号（`;`）结尾。

### 简化查询

#### $db->simpleQuery()

`simpleQuery()` 方法是 `$db->query()` 方法的简化版本。它不会返回数据库结果集，也不会设置查询计时器、编译绑定数据或存储查询以供调试。它只是让你提交一个查询。大多数用户很少会使用此函数。

它返回数据库驱动程序“execute”函数返回的任何内容。 对于 INSERT、DELETE 或 UPDATE 等写入类型查询，通常在成功或失败时返回 true/false（这正是它真正应该被用于的场景）；对于具有可获取结果的查询，在成功时返回资源/对象。

    <?php

    if ($db->simpleQuery('YOUR QUERY')) {
        echo 'Success!';
    } else {
        echo 'Query failed!';
    }

备注

PostgreSQL 的 `pg_exec()` 函数（例如）在成功时总是返回一个资源，即使是写入类型查询也是如此。 因此，如果你在寻找布尔值，请记住这一点。

## 手动处理数据库前缀

### $db->prefixTable()

如果你已配置了数据库前缀，并希望将其添加到表名前（例如在原生 SQL 查询中使用），则可以使用以下方法：

    <?php

    $db->prefixTable('tablename'); // outputs prefix_tablename

### $db->setPrefix()

如果出于任何原因，你希望在不创建新连接的情况下以编程方式更改前缀，可以使用此方法：

    <?php

    $db->setPrefix('newprefix_');
    $db->prefixTable('tablename'); // outputs newprefix_tablename

### $db->getPrefix()

你可以随时使用此方法获取当前前缀：

    <?php

    $DBPrefix = $db->getPrefix();

## 保护标识符

### $db->protectIdentifiers()

在许多数据库中，建议保护表名和字段名——例如在 MySQL 中使用反引号。查询构建器（Query Builder）查询会自动受到保护，但如果你需要手动保护标识符，可以使用：

    <?php

    $db->protectIdentifiers('table_name');

重要

尽管查询构建器会尽力正确地引用你提供的任何字段和表名。请注意，它并非设计用于处理任意的用户输入。切勿将未经净化的用户数据提供给它。

此函数还会为你的表添加 表前缀，前提是你的数据库配置文件中指定了前缀。要启用前缀功能，请通过第二个参数设置为 `true` （布尔值）：

    <?php

    $db->protectIdentifiers('table_name', true);

## 转义值

在将数据提交到数据库之前进行转义是一种非常好的安全实践。CodeIgniter 提供了三种方法来帮助你实现这一点：

### 1. $db->escape()

此函数会确定数据类型，以便仅转义字符串数据。它还会自动在数据周围添加单引号，因此你无需手动添加：

    <?php

    $sql = 'INSERT INTO table (title) VALUES(' . $db->escape($title) . ')';

### 2. $db->escapeString()

此函数会转义传递给它的数据，无论其类型如何。大多数情况下，你将使用上面的函数而不是这个。使用此函数的方法如下：

    <?php

    $sql = "INSERT INTO table (title) VALUES('" . $db->escapeString($title) . "')";

### 3. $db->escapeLikeString()

当字符串用于 LIKE 条件时，应使用此方法，以确保字符串中的 LIKE 通配符（`%`、`_`）也得到正确转义。

    <?php

    $search = '20% raise';
    $sql    = "SELECT id FROM table WHERE column LIKE '%" . $db->escapeLikeString($search) . "%' ESCAPE '!'";

重要

`escapeLikeString()` 方法使用 `'!'` （感叹号）来转义 `LIKE` 条件中的特殊字符。由于此方法转义的是你自己需要用引号包裹的部分字符串，因此它无法自动为你添加 `ESCAPE '!'` 条件，所以你需要手动完成。

## 查询绑定

绑定功能通过让系统为你组合查询，从而简化你的查询语法。请看以下示例：

    <?php

    $sql = 'SELECT * FROM some_table WHERE id = ? AND status = ? AND author = ?';
    $db->query($sql, [3, 'live', 'Rick']);

查询中的问号会自动被查询函数第二个参数数组中的值所替换。

绑定也适用于数组，数组将被转换为 IN 集合：

    <?php

    $sql = 'SELECT * FROM some_table WHERE id IN ? AND status = ? AND author = ?';
    $db->query($sql, [[3, 6], 'live', 'Rick']);

最终生成的查询将为:

    SELECT * FROM some_table WHERE id IN (3,6) AND status = 'live' AND author = 'Rick'

使用绑定的另一个好处是，值会被自动转义，从而生成更安全的查询。 你无需记住要手动转义数据——引擎会自动为你完成。

### 命名绑定

你可以为绑定命名，而不是使用问号来标记绑定值的位置，这样传递的值的键就可以与查询中的占位符相匹配：

    <?php

    $sql = 'SELECT * FROM some_table WHERE id = :id: AND status = :status: AND author = :name:';
    $db->query($sql, [
        'id'     => 3,
        'status' => 'live',
        'name'   => 'Rick',
    ]);

备注

查询中的每个名称必须用冒号包围。

## 处理错误

### $db->error()

如果你需要获取最近发生的错误，`error()` 方法将返回一个包含其代码和消息的数组。这里有一个快速示例：

    <?php

    if (! $db->simpleQuery('SELECT `example_field` FROM `example_table`')) {
        $error = $db->error(); // Has keys 'code' and 'message'
    }

## 预处理查询

大多数数据库引擎都支持某种形式的预处理语句，允许你先准备一次查询，然后使用新的数据集多次执行该查询。由于数据是以与查询本身不同的格式传递给数据库的，这消除了 SQL 注入的可能性。当你需要多次运行同一查询时，这种方式也可能快得多。然而，对每个查询都使用预处理可能会造成重大的性能损失，因为你会更频繁地调用数据库。由于查询构建器（Query Builder）和数据库连接已经为你处理了数据转义，安全性方面已经为你解决了。不过，有时你可能需要通过运行预处理语句或预处理查询来优化查询。

### 准备查询

这可以通过 `prepare()` 方法轻松完成。该方法接受一个参数，即返回查询对象的闭包（Closure）。查询对象会由任何“最终”类型的查询自动生成，包括 insert、update、delete、replace 和 get。使用查询构建器来运行查询是最简单的方法。查询实际上不会执行，值也不重要，因为它们永远不会被应用，而是作为占位符。这将返回一个 PreparedQuery 对象：

    <?php

    $pQuery = $db->prepare(static fn ($db) => $db->table('user')->insert([
        'name'    => 'x',
        'email'   => 'y',
        'country' => 'US',
    ]));

如果你不想使用查询构建器，可以使用问号作为值占位符，手动创建查询对象：

    <?php

    use CodeIgniter\Database\Query;

    $pQuery = $db->prepare(static function ($db) {
        $sql = 'INSERT INTO user (name, email, country) VALUES (?, ?, ?)';

        return (new Query($db))->setQuery($sql);
    });

如果数据库在准备语句阶段需要传入一个选项数组，你可以在第二个参数中传入该数组：

    <?php

    use CodeIgniter\Database\Query;

    $pQuery = $db->prepare(static function ($db) {
        $sql = 'INSERT INTO user (name, email, country) VALUES (?, ?, ?)';

        return (new Query($db))->setQuery($sql);
    }, $options);

备注

目前，真正使用选项数组的数据库只有 SQLSRV。

### 执行查询

一旦你有了一个预处理查询，就可以使用 `execute()` 方法来实际运行查询。你可以在查询参数中传入任意数量的变量。你传入的参数数量必须与查询中的占位符数量相匹配。它们还必须按照占位符在原始查询中出现的顺序传入：

    <?php

    // Prepare the Query
    $pQuery = $db->prepare(static fn ($db) => $db->table('user')->insert([
        'name'    => 'x',
        'email'   => 'y',
        'country' => 'US',
    ]));

    // Collect the Data
    $name    = 'John Doe';
    $email   = 'j.doe@example.com';
    $country = 'US';

    // Run the Query
    $results = $pQuery->execute($name, $email, $country);

对于“写入”类型的查询，它返回 true 或 false，表示查询的成功或失败。 对于“读取”类型的查询，它返回一个标准的 结果集。

### 其他方法

除了这两个主要方法外，预处理查询对象还具有以下方法：

#### close()

虽然 PHP 在关闭所有与数据库的打开语句方面做得相当好，但在你完成使用后关闭预处理语句始终是个好主意：

    <?php

    if ($pQuery->close()) {
        echo 'Success!';
    } else {
        echo 'Deallocation of prepared statements failed!';
    }

备注

自 v4.3.0 版本起，`close()` 方法在所有数据库管理系统（DBMS）中都会释放预处理语句。在此之前，PostgreSQL、SQLSRV 和 OCI8 中不会释放。

#### getQueryString()

此方法将预处理查询作为字符串返回。

#### hasError()

如果上一次 `execute()` 调用产生了任何错误，则返回布尔值 true/false。

#### getErrorCode()

#### getErrorMessage()

如果遇到任何错误，可以使用这些方法来检索错误代码和错误信息。

## 使用查询对象

在内部，所有查询都会作为 `CodeIgniter\Database\Query` 的实例进行处理和存储。此类负责绑定参数、准备查询以及存储有关其查询的性能数据。

### $db->getLastQuery()

当你只需要检索最后一个查询对象时，使用 `getLastQuery()` 方法：

    <?php

    $query = $db->getLastQuery();
    echo (string) $query;

### 查询类

每个查询对象都存储了有关查询本身的一些信息。 这在一定程度上被时间线（Timeline）功能所使用，但也可以供你使用。

#### getQuery()

返回所有处理完成后最终的查询。这是实际发送到数据库的确切查询：

    <?php

    $sql = $query->getQuery();

通过将查询对象转换为字符串，也可以获得相同的值：

    <?php

    $sql = (string) $query;

#### getOriginalQuery()

返回传递给对象的原始 SQL。其中不会包含任何绑定、前缀替换等内容：

    <?php

    $sql = $query->getOriginalQuery();

#### hasError()

如果在执行此查询期间遇到错误，此方法将返回 true：

    <?php

    if ($query->hasError()) {
        echo 'Code: ' . $query->getErrorCode();
        echo 'Error: ' . $query->getErrorMessage();
    }

#### isWriteType()

如果确定查询为写入类型查询（即 INSERT、UPDATE、DELETE 等），则返回 true：

    <?php

    if ($query->isWriteType()) {
        // ... do something
    }

#### swapPrefix()

在 SQL 中将一个表前缀替换为另一个值。第一个参数是你想要替换的原始前缀，第二个参数是你想要将其替换为的值：

    <?php

    $sql = $query->swapPrefix('ci3_', 'ci4_');

#### getStartTime()

获取查询执行的时间（以秒为单位，包含微秒）：

    <?php

    $microtime = $query->getStartTime();

#### getDuration()

返回一个浮点数，表示查询持续的时间（以秒为单位，包含微秒）：

    <?php

    $microtime = $query->getDuration();