Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Execute the query, handle errors separately, then fetch a row. A successful SELECT can still return zero rows, so checking only whether the query call was truthy is not enough. In modern PHP, use MySQLi or PDO; the old mysql_* extension was removed in PHP 7.0.

For PDO, the usual pattern is:

$stmt = $pdo->prepare(
    'SELECT id, name FROM users WHERE email = :email'
);
$stmt->execute(['email' => $email]);

$row = $stmt->fetch(PDO::FETCH_ASSOC);

if ($row === false) {
    // Query succeeded, but no matching row exists.
} else {
    // Process $row.
}

Empty results and query errors are different

These are three separate outcomes:

Outcome MySQLi PDO
SQL error query() returns false PDOException when exception mode is enabled
Successful query, zero rows A result object is returned; the first fetch returns no row fetch() returns false
Successful query, rows found A fetch returns an array fetch() returns an array

Do not combine these cases into one conditional. An error should be logged or reported as an error, not presented to a user as “no matches.”

MySQLi: fetch the first row

When you will process the result, fetching the first row is usually the clearest test:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
$result = $mysqli->query(
    'SELECT id, name FROM users WHERE email = ?'
);

if ($result === false) {
    throw new RuntimeException($mysqli->error);
}

$row = $result->fetch_assoc();

if ($row === null) {
    // No matching row.
} else {
    // Use $row['id'] and $row['name'].
}

See the MySQLi fetch documentation for the result-fetching API.

Prepared MySQLi statements with get_result()

$stmt = $mysqli->prepare(
    'SELECT id, name FROM users WHERE email = ?'
);
$stmt->bind_param('s', $email);
$stmt->execute();

$result = $stmt->get_result();
$row = $result->fetch_assoc();

if ($row === null) {
    // No match.
}

get_result() requires the MySQL Native Driver (mysqlnd). On systems without it, use store_result(), num_rows, and bind_result():

$stmt->execute();
$stmt->store_result();

if ($stmt->num_rows === 0) {
    // No rows.
} else {
    $stmt->bind_result($id, $name);
    while ($stmt->fetch()) {
        // Process the values.
    }
}

When mysqli_num_rows() is appropriate

For a buffered MySQLi result, this is valid:

$result = $mysqli->query($sql);

if ($result === false) {
    throw new RuntimeException($mysqli->error);
}

if ($result->num_rows === 0) {
    // Successful query, no rows.
}

The procedural equivalent is mysqli_num_rows($result) === 0. If you will iterate through the rows anyway, fetching the first row and continuing is more direct. With an unbuffered result, the row count may not be available until rows have been fetched; see the MySQLi row-count documentation.

PDO: use fetch(), not rowCount()

Configure PDO to throw execution errors:

$pdo = new PDO($dsn, $username, $password, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

Then fetch one row:

$stmt = $pdo->prepare(
    'SELECT id, name FROM users WHERE email = :email'
);
$stmt->execute(['email' => $email]);

$row = $stmt->fetch();

if ($row === false) {
    // No matching row.
} else {
    // Process $row.
}

PDOStatement::rowCount() is intended primarily for affected rows from INSERT, UPDATE, and DELETE. Its result for SELECT is undefined and driver-dependent, so it is not a portable empty-result test. Use fetch() instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why not fetchAll()?

fetchAll() returns an empty array when there are no rows, but it also loads every remaining row into PHP memory. Use it when you genuinely need the complete array, not just to discover whether one row exists.

Choose SQL that matches the question

Requirement SQL and PHP approach
Retrieve one matching record SELECT id, name ... LIMIT 1, then fetch the row
Only test existence SELECT 1 ... LIMIT 1 or SELECT EXISTS (...)
Get an exact count SELECT COUNT(*) ...
Process every match Run the normal SELECT and iterate; track whether any row was processed

Existence-only query with PDO

$stmt = $pdo->prepare(
    'SELECT 1 FROM users WHERE email = :email LIMIT 1'
);
$stmt->execute(['email' => $email]);

$exists = $stmt->fetchColumn() !== false;

This avoids selecting columns the application does not need. An equivalent MySQLi query is:

$stmt = $mysqli->prepare(
    'SELECT 1 FROM users WHERE email = ? LIMIT 1'
);
$stmt->bind_param('s', $email);
$stmt->execute();

$result = $stmt->get_result();
$exists = $result->fetch_row() !== null;

SELECT EXISTS (...) is another clear expression of a Boolean requirement:

$stmt = $pdo->prepare(
    'SELECT EXISTS (SELECT 1 FROM users WHERE email = :email)'
);
$stmt->execute(['email' => $email]);
$exists = (bool) $stmt->fetchColumn();

Use strict comparisons with fetchColumn(): a returned value such as 0 or an empty string can be falsey even when a row exists.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Counting versus checking existence

If the application needs the exact number of matches, ask MySQL for that scalar:

$stmt = $pdo->prepare(
    'SELECT COUNT(*) FROM users WHERE status = :status'
);
$stmt->execute(['status' => 'active']);
$count = (int) $stmt->fetchColumn();

COUNT(*) answers a different question from “does at least one row exist?” It may need to account for all matching rows, while a single-row or EXISTS query expresses an existence-only requirement. Actual performance depends on indexes, predicates, table size, isolation, and the optimizer.

Common mistakes

  • Checking only the query return value: a successful SELECT can contain zero rows.
  • Conflating errors and emptiness: test false from execution before testing rows.
  • Using rowCount() for PDO SELECT: behavior is not portable.
  • Loading everything with fetchAll(): fetch one row when that is all you need.
  • Using SELECT * for existence: select a constant such as 1.
  • Interpolating input: use prepared statements and bound parameters.
  • Ignoring indexes: frequently tested columns such as email, account ID, or order number should have suitable indexes.

Legacy mysql_* code

Code such as mysql_query() and mysql_num_rows() belongs to PHP’s original MySQL extension. It was deprecated in PHP 5.5 and removed in PHP 7.0. Migrate to MySQLi or PDO_MySQL rather than adding new code with that API. See the PHP manual’s original MySQL extension notice.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.