What is the purpose of 'LIMIT 0,1' on this query? - php

I'm following an API tutorial (user authentication), but i'm fairly new to SQL, so there are some stuff that i dont understand.
I've been looking for an answer and as far as i know, the LIMIT clause has an offset (0 in this case) and a count (1 in this case).
This is the code (inside the user class):
function emailExists(){
// query to check if email exists
$query = "SELECT id, firstname, lastname, password
FROM " . $this->table_name . "
WHERE email = ?
LIMIT 0,1";
// prepare the query
$stmt = $this->conn->prepare( $query );
// sanitize
$this->email=htmlspecialchars(strip_tags($this->email));
// bind given email value
$stmt->bindParam(1, $this->email);
// execute the query
$stmt->execute();
// get number of rows
$num = $stmt->rowCount();
// if email exists, assign values to object properties for easy access and use for php sessions
if($num>0){
// get record details / values
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// assign values to object properties
$this->id = $row['id'];
$this->firstname = $row['firstname'];
$this->lastname = $row['lastname'];
$this->password = $row['password'];
// return true because email exists in the database
return true;
}
// return false if email does not exist in the database
return false;
}
What i understand is that the query starts looking for a similar email from the start (row 0), but then i don't understand why they bind the 1 to the email.
Is it necessary to use a LIMIT here? why can't just use the clause WHERE email = :email (and bind the :email to the email sent by the user?)

Query checks whether email exists only. If there is more records with the same email it takes more resources to execute statement. If there is at least one record it means email exists. No need to check more.
This case doesn't show it clearly but imagine you have table with millions of records and you want to check whether one specific value exists which can appear in multiple records. You can freeze database if query is too complicated /too many tables are joint etc. You need only to check, so you limit it to 1. This is nice practice to this type of queries.

Related

Using mysqli query object as a set in subsequent query

I am working on a reservation system for a local resort which is accessed via reasonably secure LAN only, as such I am not immediately concerned with SQL injection. My main concern is the functionality to lookup up reservations based upon the PK(s) returned by a query on individual guests. To do this, I have been using the mysqli query object returned by a query of a table containing guest information:
$get_guest_id = "SELECT id FROM guests WHERE fname = '$fname' OR lname = '$lname' OR phone = '$phone' OR email = '$email'";
$guest_id_result = mysqli_query($con, $get_guest_id);
I have tried using this query object as I would a set within mySQL:
$search_by_id = "SELECT * FROM reservation WHERE guest_id IN '$guest_id_result'";
But this does not return as expected:
Returns false on failure. For successful queries which produce a result set, such as SELECT, SHOW, DESCRIBE or EXPLAIN, mysqli_query() will return a mysqli_result object. For other successful queries, mysqli_query() will return true.
Rather than returning true, false, or a result set, the PHP script stops executing at this statement.
Is there a different way to pass the data contained within a mysqli query object to another query, in a manner similar to a set?
The problem is that you're trying to cast an object into a string for your query...
The best solution would probably be to turn the two queries into a singular one, additionally updating the code to use a prepared statement.
Query
SELECT * FROM reservations
WHERE guest_id IN (SELECT id
FROM guests
WHERE fname = ?
OR lname = ?
OR phone = ?
OR email = ?
);
Code
$sql = "
SELECT * FROM reservations
WHERE guest_id IN (SELECT id
FROM guests
WHERE fname = ?
OR lname = ?
OR phone = ?
OR email = ?
);
";
$query = $con->prepare($sql);
$query->bind_param("ssss", $fname, $lname, $phone, $email);
$query->execute();
$result = $query->get_result();
while ($row = $result->fetch_assoc()) {
echo $row["guest_id"], PHP_EOL; // Example output printing the guest_id of guests with reservations (followed by a newline)
}

SELECT count(*) with OR operator

I'm trying to use SELECT COUNT(*) in a prepared statement.
Below is my PHP:
if(!($stmt = $link->prepare("SELECT COUNT(*) AS failed FROM LoginAttempts WHERE (email = ? OR IP = ?) AND LastLogin BETWEEN now() and subdate(now(),INTERVAL 5 MINUTE)"))){
}
$stmt->bind_param('ss', $email, $ip);
$stmt->execute();
$stmt->bind_result($failed);
$stmt->close();
What I'm trying to achieve from this, is when a user tries to login to their account (and provides incorrect login information) their IP, and the entered email address is logged in a table.
When an attempt is tried to login, using their email or IP, I count how many records match either their IP or email address.
The issue I'm having, is when accessing $failed the result is NULL even though there are records in the database within the last 5 minutes.
Where exactly am I going wrong? There are no errors in my apache error log, or with:
error_reporting(E_ALL);
ini_set('display_errors',1);
Thanks for your time
Edit: I needed to use $stmt->fetch(); - not sure how I missed that. Thanks to Saty for your comment!
The issue is with between clause
SELECT COUNT(*) AS failed FROM LoginAttempts
WHERE (email = ? OR IP = ?) AND LastLogin
BETWEEN subdate(now(),INTERVAL 5 MINUTE) and now()
try this and let me know
Few mistake in your code
1) Not looking for errors
2) Close if condition at the end
3) Forget to fetch data form query result
You code would be
if (!($stmt = $link->prepare("SELECT COUNT(*) AS failed FROM LoginAttempts WHERE (email = ? OR IP = ?) AND LastLogin BETWEEN now() and subdate(now(),INTERVAL 5 MINUTE)"))) {
/* bind parameters for markers */
$stmt->bind_param('ss', $email, $ip);
/* execute query */
$stmt->execute();
/* bind result variables */
$stmt->bind_result($failed);
/* fetch value */
while ($stmt->fetch()) {
printf("%s", $failed);
}
/* close statement */
$stmt->close();
}
A few errors in here, but mainly you forgot to fetch the result after binding it to a variable. bind_param basically indicates the variable to place the values from the result set it does not actually do the retrieval of a row from the result set, you need some sort of ->fetch() to do that
Its a good idea to test all the status's of almost all mysqli_ api calls and output the error somewhere, they are normally very useful and fairly precise. A great aid in debugging a complex query
$stmt = $link->prepare("SELECT COUNT(*) AS failed
FROM LoginAttempts
WHERE (email = ? OR IP = ?)
AND LastLogin BETWEEN now() and subdate(now(),INTERVAL 5 MINUTE)");
if ( $stmt === FALSE ) {
echo $link->error;
exit;
}
$stmt->bind_param('ss', $email, $ip);
$result = $stmt->execute();
if ( $result === false ) {
echo $link->error;
exit;
}
$stmt->bind_result($failed);
$stmt->fetch(); // this actually get the value out of the result set into $failed
$stmt->close();

Email is already in database, returns as if it isn't

Here is some background information on what I'm trying to do here. I'm try to create a registration form for my website (successful so far until this point). Data can be entered into the DB. The only thing I'm trying to do now is prevent duplicate email/usernames from being entered into the db. Through much stackoverflow research, I have found and tested the following code:
$query = "SELECT COUNT(*) AS num_rows FROM users WHERE email = ?";
$stmt = $mysqli->prepare($query);
$stmt->bind_param("s", $email);
if ($stmt->execute()) {
return $stmt->num_rows;
}
What I then do is following:
if(user_exists($user_email) > 0) {
echo "Email already exists!";
}
But is passes by this if statement as if the email does exist in the database!
The email I'm trying to enter, for my tests is testemail#testemailweb.com which is already in the database! Would someone possibly point out where I have messed up in my code? Is there a silly mistake that I could have possibly done when trying to perform this?
The fix for your particular problem here is by not using COUNT(*) as mentioned by John and not depend on mysqli_stmt->num_rows by using a buffered result set:
$query = "SELECT * FROM users WHERE email = ?";
$stmt = $mysqli->prepare($query);
$stmt->bind_param("s", $email);
return $stmt->execute() && $stmt->store_result() && $stmt->num_rows > 0;
Addendum
The only thing I'm trying to do now is prevent duplicate email/usernames from being entered into the db
You will want to use table constraints to prevent this (not just from the app, but anything else that can access the database). This has the added benefit of guarding against race conditions.
ALTER TABLE users ADD UNIQUE(email);
This will raise an error if you attempt to insert a row with an email value that already exists. You can check for this error on the application side and do whatever you want with it.
Your query will always return 1 row. COUNT(*) will return a result set even if only to report no rows match your query. As a result user_exists() always returns 1.
Change your query to:
$query = "SELECT * FROM users WHERE email = ?";
Now if no rows match your query $stmt->num_rows will be 0 so user_exists() will return 0.
change:
if ($stmt->execute()) {
return $stmt->num_rows;
}
What I then do is following:
if(user_exists($user_email) > 0) {
echo "Email already exists!";
}
to
$j=0;
if ($stmt->execute()) {
$j= $stmt->num_rows;
} else {
echo "Email already exists!";
}

PHP - Unable to retrieve data from the database

For some reason, the query when run through PHP will not return the results. I have tried both queries in the MySQL command line, and they work perfectly there. Here is the code (mysql_connect.php is working perfectly, to clarify).
<?php
error_reporting(-1);
// retrieve email from cookie
$email = $_COOKIE['email'];
// connect to mysql database
require('mysql_connect.php');
// get user_id by searching for the email it corresponds to
$id = mysqli_query($dbc,"SELECT user_id FROM users WHERE email=$email")or die('couldn\'t get id');
// get data by using the user_id in $id
$result = mysqli_query($dbc,"SELECT * FROM users WHERE user_id=$id")or die('couldn\'t get data');
//test if the query failed
if($result === FALSE) {
die(mysql_error());
echo("error");
}
// collect the array of results and print the ones required
while($row = mysql_fetch_array($result)) {
echo $row['first_name'];
}
?>
When I run the script, I get the message "could not get id", yet that query works in the MySQL command line and PHPMyAdmin.
Your code won't work for 2 reasons - $id will not magically turn into integer, but a mysqli result. And email is a string so it should be quoted.
But...
Why is all of that?
If you want to fetch all the data for user, for certain email, just make you second query fetch data by email and remove the first one:
SELECT * FROM users WHERE email='$email';
And don't forget to escape your input, because it's in cookie. Or, use prepared statements as suggested.
Your query is not valid, you should rewrite it with the following and make sure your you have mysqli_real_escape_string of the $email value before you put it into queries:
SELECT user_id FROM users WHERE email='$email'
Better approach is to rewrite your queries using MySQLi prepared statements:
Here how to get the $id value:
$stmt = mysqli_prepare($dbc, "SELECT user_id FROM users WHERE email = ?");
mysqli_stmt_bind_param($stmt, "s", $email);
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $id);
mysqli_stmt_fetch($stmt);
You wrote
mysqli_query($dbc,"SELECT user_id FROM users WHERE email=$email");
that is similar to
mysqli_query($dbc,"SELECT user_id FROM users WHERE email=example#example.com");
but it should be
mysqli_query($dbc,"SELECT user_id FROM users WHERE email='example#example.com'");
so you have to do this
mysqli_query($dbc,"SELECT user_id FROM users WHERE email='$email'");
or better
mysqli_query($dbc, 'SELECT user_id FROM users WHERE email=\'' . $email . '\'');
Beside this minor bug
You should be aware of SQL injection if someone changes the value of your cookie.

Prepared statement and null value when using?

I have a need to evaluate if a user logged in to a system after a specific date. To do this, there are three tables in a MySQL database, users, survey and logins. Survey holds the date of a point in time that needs compared against the users last log in. Here's the question.
When I used the "?" placeholder, the resulting num_rows count was always 0. But when I assign the values before handing the query statement to $mysqli->prepare(), the process works as expected. Somehow, store_result() was not picking up the column. Here is my code:
if (isset($userId)){
//get survey release date
$res3 = $mysqli->query("SELECT sur_date,sur_url FROM survey ORDER BY sur_id DESC limit 1");
$array = $res3->fetch_assoc();
$theta_date = $array['sur_date'];
//$theta_date = "2013-01-18 01:00:00";
//this didn't generate errors, but didn't output the correct result either.
//$query = "SELECT login_id FROM logins WHERE login_user=? AND login_date>=?";
//if ($stmt = $mysqli->prepare($query)){
// $stmt->bind_param('ss',$userID,$theda_date);
// $stmt->execute();
//this works
$query = "SELECT login_id FROM logins WHERE login_user='$userId' AND login_date>='$theta_date'";
if ($stmt = $mysqli->prepare($query)){
$stmt->execute() or die("The query did not work");
//if number is greater than 0 do something
$stmt->store_result();
printf("The number of login ids after theta are %d",$stmt->num_rows);
$stmt->close();
}else{
echo "The query did not execute.";
}
}else{
echo "The User ID was not valid.";
exit();
}
$mysqli->close();
Any insight would be helpful,
The prepared statement seems to be having an issue with the $theta_date datetime format. $theta_date is stored in the survey table as '2013-01-18 01:00:00'. bind_param() was trying to parse $theta_date as a reference. Here is the solution:
//convert datetime to Unix timestamp with strtotime()
$theta_date = $array['sur_date'];
$timestamp = strtotime($theta_date);
//In the prepared statement, use MySQL FROM_UNIXTIME function to convert timestamp
$query = "SELECT login_id FROM logins WHERE login_user=? AND login_date>=FROM_UNIXTIME(?)";
//changed the parameter types to integer and bound $timestamp to second placeholder
if ($stmt = $mysqli->prepare($query)){
$stmt->bind_param('ii',$userId,$timestamp);
$stmt->execute();
//the rest is the same
$stmt->store_result();
printf("The number of login ids after theta are %d",$stmt->num_rows);
$stmt->close();
}
That was a pain.

Categories