Recommended Free Tools
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:
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match$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.
#1 Best Overall
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.
Rank #2
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.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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:
Rank #4
$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.
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
SELECTcan contain zero rows. - Conflating errors and emptiness: test
falsefrom execution before testing rows. - Using
rowCount()for PDOSELECT: 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 as1. - 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.
Quick Recap
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.

