Posteriormente a consultar con especialistas en el tema, programadores de diversas ramas y profesores dimos con la respuesta a la cuestión y la dejamos plasmada en esta publicación.
Solución:
Usar
$where = "WHERE user_id = '$username'";
if(!empty($value1))
$where .= "and location = '$value1'";
if(!empty($value2 ))
$where .= "and english_name= '$value2 '";
$query = "SELECT * FROM moth_sightings $where";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Varias otras respuestas mencionan el riesgo de inyección de SQL, y un par mencionan explícitamente el uso de declaraciones preparadas, pero ninguna de ellas muestra explícitamente cómo podría hacerlo, lo que podría ser una gran pregunta para un principiante.
Mi método preferido actual para resolver este problema utiliza una declaración “IF” de MySQL para verificar si el parámetro en cuestión es null/vacío/0 (dependiendo del tipo). Si está vacío, compara el valor del campo consigo mismo ( WHERE field1=field1
siempre regresa true
). Si el parámetro no está vacío/null/cero, el valor del campo se compara con el parámetro.
Entonces, aquí hay un ejemplo que usa declaraciones preparadas de MySQLi (suponiendo que $mysqli es un objeto mysqli ya instanciado):
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = ?
AND location = IF(? = '', location, ?)
AND english_name = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('ssss', $username, $value1, $value1, $value2);
$stmt->execute();
(supongo que $value2
es un string basado en el nombre del campo, a pesar de la falta de comillas en el SQL de ejemplo de OP).
No hay forma en MySQLi de vincular el mismo parámetro a múltiples marcadores de posición dentro de la declaración, por lo que tenemos que vincular explícitamente $value1
dos veces. La ventaja que tiene MySQLi en este caso es la tipificación explícita del parámetro – si pasamos $value1
como un stringsabemos que necesitamos compararlo con el vacío string ''
. Si $value1
fuera un valor entero, podríamos declararlo explícitamente así:
$stmt->bind_param('siis', $username, $value1, $value1, $value2);
y compararlo contra 0
en lugar de.
Aquí hay un ejemplo de PDO que usa parámetros con nombre, porque creo que dan como resultado un código mucho más legible con menos conteo:
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = :user_id
AND location = IF(:location_id = '', location, :location_id)
AND english_name = :name";
$stmt = $pdo->prepare($sql);
$params = [
':user_id' => $username,
':location_id' => $value1,
':name' => $value2
];
$stmt->execute($params);
Tenga en cuenta que con los parámetros con nombre PDO, podemos referirnos a :location_id
varias veces en la consulta y solo tiene que vincularla una vez.