Related
Here is a snapshot of my code:
$fetchPictures = $PDO->prepare("SELECT *
FROM pictures
WHERE album = :albumId
ORDER BY id ASC
LIMIT :skip, :max");
$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);
if(isset($_GET['skip'])) {
$fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);
} else {
$fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);
}
$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);
I get
You have an error in your SQL syntax;
check the manual that corresponds to
your MySQL server version for the
right syntax to use near ''15', 15' at
line 1
It seems that PDO is adding single quotes to my variables in the LIMIT part of the SQL code. I looked it up I found this bug which I think is related:
http://bugs.php.net/bug.php?id=44639
Is that what I'm looking at? This bug has been opened since April 2008!
What are we supposed to do in the meantime?
I need to build some pagination, and need to make sure the data is clean, sql injection-safe, before sending the sql statement.
I remember having this problem before. Cast the value to an integer before passing it to the bind function. I think this solves it.
$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
The simplest solution would be to switch the emulation mode off. You can do it by simply adding the following line
$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
Also, this mode can be set as a constructor parameter when creating a PDO connection. It could be a better solution as some report their driver doesn't support the setAttribute() function.
It will not only solve your problem with binding, but also let you send values directly into execute(), which will make your code dramatically shorter. Assuming the emulation mode has been already set, the whole affair will take as much as half a dozen lines of code
$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);
Looking at the bug report, the following might work:
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);
but are you sure your incoming data is correct? Because in the error message, there seems to be only one quote after the number (as opposed to the whole number being enclosed in quotes). This could also be an error with your incoming data. Can you do a print_r($_GET); to find out?
This just as summary.
There are four options to parameterize LIMIT/OFFSET values:
Disable PDO::ATTR_EMULATE_PREPARES as mentioned above.
Which prevents values passed per ->execute([...]) to always show up as strings.
Switch to manual ->bindValue(..., ..., PDO::PARAM_INT) parameter population.
Which however is less convenient than an ->execute list[].
Simply make an exception here and just interpolate plain integers when preparing the SQL query.
$limit = intval($limit);
$s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");
The casting is important. More commonly you see ->prepare(sprintf("SELECT ... LIMIT %d", $num)) used for such purposes.
If you're not using MySQL, but for example SQLite, or Postgres; you can also cast bound parameters directly in SQL.
SELECT * FROM tbl LIMIT (1 * :limit)
Again, MySQL/MariaDB don't support expressions in the LIMIT clause. Not yet.
for LIMIT :init, :end
You need to bind that way. if you had something like $req->execute(Array()); it wont work as it will cast PDO::PARAM_STR to all vars in the array and for the LIMIT you absolutely need an Integer.
bindValue or BindParam as you want.
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
Since nobody has explained why this is happening, I'm adding an answer. The reason it is behaving this was is because you are using trim(). If you look at the PHP manual for trim, the return type is string. You are then trying to pass this as PDO::PARAM_INT. A few ways to get around this are:
Use filter_var($integer, FILTER_VALIDATE_NUMBER_INT) to make sure you are passing an integer.
As others said, using intval()
Casting with (int)
Checking if it is an integer with is_int()
There are plenty more ways, but this is basically the root cause.
bindValue offset and limit using PDO::PARAM_INT and it will work
//BEFORE (Present error)
$query = " .... LIMIT :p1, 30;";
...
$stmt->bindParam(':p1', $limiteInferior);
//AFTER (Error corrected)
$query = " .... LIMIT :p1, 30;";
...
$limiteInferior = (int)$limiteInferior;
$stmt->bindParam(':p1', $limiteInferior, PDO::PARAM_INT);
PDO::ATTR_EMULATE_PREPARES gave me the
Driver does not support this function: This driver doesn't support
setting attributes' error.
My workaround was to set a $limit variable as a string, then combine it in the prepare statement as in the following example:
$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
$stmt->execute( array( ':cid' => $company_id ) );
...
}
catch ( Exception $e ) {
...
}
There is alot going on between different versions of PHP and the oddities of PDO.
I tried 3 or 4 methods here but could not get LIMIT working.
My suggestion is to use string formatting / concatination WITH an intval() filter:
$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';
It is very important to use intval() to prevent SQL injection, particularly if you are getting your limit from $_GET or the like. If you do that this is the easiest way to get LIMIT working.
There is alot of talk about 'The problem with LIMIT in PDO' but my thought here is that PDO params were never ment to be used for LIMIT since they will alway be integers a quick filter works. Still, it is a bit misleading since the philosophy has always been to not do any SQL injection filtering yourself but rather 'Have PDO handle it'.
Try the following:
$sql2 = "SELECT * FROM tab ORDER BY id DESC limit 2
Here is a snapshot of my code:
$fetchPictures = $PDO->prepare("SELECT *
FROM pictures
WHERE album = :albumId
ORDER BY id ASC
LIMIT :skip, :max");
$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);
if(isset($_GET['skip'])) {
$fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);
} else {
$fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);
}
$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);
I get
You have an error in your SQL syntax;
check the manual that corresponds to
your MySQL server version for the
right syntax to use near ''15', 15' at
line 1
It seems that PDO is adding single quotes to my variables in the LIMIT part of the SQL code. I looked it up I found this bug which I think is related:
http://bugs.php.net/bug.php?id=44639
Is that what I'm looking at? This bug has been opened since April 2008!
What are we supposed to do in the meantime?
I need to build some pagination, and need to make sure the data is clean, sql injection-safe, before sending the sql statement.
I remember having this problem before. Cast the value to an integer before passing it to the bind function. I think this solves it.
$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
The simplest solution would be to switch the emulation mode off. You can do it by simply adding the following line
$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
Also, this mode can be set as a constructor parameter when creating a PDO connection. It could be a better solution as some report their driver doesn't support the setAttribute() function.
It will not only solve your problem with binding, but also let you send values directly into execute(), which will make your code dramatically shorter. Assuming the emulation mode has been already set, the whole affair will take as much as half a dozen lines of code
$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);
Looking at the bug report, the following might work:
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);
but are you sure your incoming data is correct? Because in the error message, there seems to be only one quote after the number (as opposed to the whole number being enclosed in quotes). This could also be an error with your incoming data. Can you do a print_r($_GET); to find out?
This just as summary.
There are four options to parameterize LIMIT/OFFSET values:
Disable PDO::ATTR_EMULATE_PREPARES as mentioned above.
Which prevents values passed per ->execute([...]) to always show up as strings.
Switch to manual ->bindValue(..., ..., PDO::PARAM_INT) parameter population.
Which however is less convenient than an ->execute list[].
Simply make an exception here and just interpolate plain integers when preparing the SQL query.
$limit = intval($limit);
$s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");
The casting is important. More commonly you see ->prepare(sprintf("SELECT ... LIMIT %d", $num)) used for such purposes.
If you're not using MySQL, but for example SQLite, or Postgres; you can also cast bound parameters directly in SQL.
SELECT * FROM tbl LIMIT (1 * :limit)
Again, MySQL/MariaDB don't support expressions in the LIMIT clause. Not yet.
for LIMIT :init, :end
You need to bind that way. if you had something like $req->execute(Array()); it wont work as it will cast PDO::PARAM_STR to all vars in the array and for the LIMIT you absolutely need an Integer.
bindValue or BindParam as you want.
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
Since nobody has explained why this is happening, I'm adding an answer. The reason it is behaving this was is because you are using trim(). If you look at the PHP manual for trim, the return type is string. You are then trying to pass this as PDO::PARAM_INT. A few ways to get around this are:
Use filter_var($integer, FILTER_VALIDATE_NUMBER_INT) to make sure you are passing an integer.
As others said, using intval()
Casting with (int)
Checking if it is an integer with is_int()
There are plenty more ways, but this is basically the root cause.
bindValue offset and limit using PDO::PARAM_INT and it will work
//BEFORE (Present error)
$query = " .... LIMIT :p1, 30;";
...
$stmt->bindParam(':p1', $limiteInferior);
//AFTER (Error corrected)
$query = " .... LIMIT :p1, 30;";
...
$limiteInferior = (int)$limiteInferior;
$stmt->bindParam(':p1', $limiteInferior, PDO::PARAM_INT);
PDO::ATTR_EMULATE_PREPARES gave me the
Driver does not support this function: This driver doesn't support
setting attributes' error.
My workaround was to set a $limit variable as a string, then combine it in the prepare statement as in the following example:
$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
$stmt->execute( array( ':cid' => $company_id ) );
...
}
catch ( Exception $e ) {
...
}
There is alot going on between different versions of PHP and the oddities of PDO.
I tried 3 or 4 methods here but could not get LIMIT working.
My suggestion is to use string formatting / concatination WITH an intval() filter:
$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';
It is very important to use intval() to prevent SQL injection, particularly if you are getting your limit from $_GET or the like. If you do that this is the easiest way to get LIMIT working.
There is alot of talk about 'The problem with LIMIT in PDO' but my thought here is that PDO params were never ment to be used for LIMIT since they will alway be integers a quick filter works. Still, it is a bit misleading since the philosophy has always been to not do any SQL injection filtering yourself but rather 'Have PDO handle it'.
Try the following:
$sql2 = "SELECT * FROM tab ORDER BY id DESC limit 2
Here is a snapshot of my code:
$fetchPictures = $PDO->prepare("SELECT *
FROM pictures
WHERE album = :albumId
ORDER BY id ASC
LIMIT :skip, :max");
$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);
if(isset($_GET['skip'])) {
$fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);
} else {
$fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);
}
$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);
I get
You have an error in your SQL syntax;
check the manual that corresponds to
your MySQL server version for the
right syntax to use near ''15', 15' at
line 1
It seems that PDO is adding single quotes to my variables in the LIMIT part of the SQL code. I looked it up I found this bug which I think is related:
http://bugs.php.net/bug.php?id=44639
Is that what I'm looking at? This bug has been opened since April 2008!
What are we supposed to do in the meantime?
I need to build some pagination, and need to make sure the data is clean, sql injection-safe, before sending the sql statement.
I remember having this problem before. Cast the value to an integer before passing it to the bind function. I think this solves it.
$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);
The simplest solution would be to switch the emulation mode off. You can do it by simply adding the following line
$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
Also, this mode can be set as a constructor parameter when creating a PDO connection. It could be a better solution as some report their driver doesn't support the setAttribute() function.
It will not only solve your problem with binding, but also let you send values directly into execute(), which will make your code dramatically shorter. Assuming the emulation mode has been already set, the whole affair will take as much as half a dozen lines of code
$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);
Looking at the bug report, the following might work:
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);
but are you sure your incoming data is correct? Because in the error message, there seems to be only one quote after the number (as opposed to the whole number being enclosed in quotes). This could also be an error with your incoming data. Can you do a print_r($_GET); to find out?
This just as summary.
There are four options to parameterize LIMIT/OFFSET values:
Disable PDO::ATTR_EMULATE_PREPARES as mentioned above.
Which prevents values passed per ->execute([...]) to always show up as strings.
Switch to manual ->bindValue(..., ..., PDO::PARAM_INT) parameter population.
Which however is less convenient than an ->execute list[].
Simply make an exception here and just interpolate plain integers when preparing the SQL query.
$limit = intval($limit);
$s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");
The casting is important. More commonly you see ->prepare(sprintf("SELECT ... LIMIT %d", $num)) used for such purposes.
If you're not using MySQL, but for example SQLite, or Postgres; you can also cast bound parameters directly in SQL.
SELECT * FROM tbl LIMIT (1 * :limit)
Again, MySQL/MariaDB don't support expressions in the LIMIT clause. Not yet.
for LIMIT :init, :end
You need to bind that way. if you had something like $req->execute(Array()); it wont work as it will cast PDO::PARAM_STR to all vars in the array and for the LIMIT you absolutely need an Integer.
bindValue or BindParam as you want.
$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);
Since nobody has explained why this is happening, I'm adding an answer. The reason it is behaving this was is because you are using trim(). If you look at the PHP manual for trim, the return type is string. You are then trying to pass this as PDO::PARAM_INT. A few ways to get around this are:
Use filter_var($integer, FILTER_VALIDATE_NUMBER_INT) to make sure you are passing an integer.
As others said, using intval()
Casting with (int)
Checking if it is an integer with is_int()
There are plenty more ways, but this is basically the root cause.
bindValue offset and limit using PDO::PARAM_INT and it will work
//BEFORE (Present error)
$query = " .... LIMIT :p1, 30;";
...
$stmt->bindParam(':p1', $limiteInferior);
//AFTER (Error corrected)
$query = " .... LIMIT :p1, 30;";
...
$limiteInferior = (int)$limiteInferior;
$stmt->bindParam(':p1', $limiteInferior, PDO::PARAM_INT);
PDO::ATTR_EMULATE_PREPARES gave me the
Driver does not support this function: This driver doesn't support
setting attributes' error.
My workaround was to set a $limit variable as a string, then combine it in the prepare statement as in the following example:
$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
$stmt->execute( array( ':cid' => $company_id ) );
...
}
catch ( Exception $e ) {
...
}
There is alot going on between different versions of PHP and the oddities of PDO.
I tried 3 or 4 methods here but could not get LIMIT working.
My suggestion is to use string formatting / concatination WITH an intval() filter:
$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';
It is very important to use intval() to prevent SQL injection, particularly if you are getting your limit from $_GET or the like. If you do that this is the easiest way to get LIMIT working.
There is alot of talk about 'The problem with LIMIT in PDO' but my thought here is that PDO params were never ment to be used for LIMIT since they will alway be integers a quick filter works. Still, it is a bit misleading since the philosophy has always been to not do any SQL injection filtering yourself but rather 'Have PDO handle it'.
Try the following:
$sql2 = "SELECT * FROM tab ORDER BY id DESC limit 2
This question already has answers here:
How can I pass an array of PDO parameters yet still specify their types?
(3 answers)
How to apply bindValue method in LIMIT clause?
(11 answers)
Closed 7 years ago.
$sql = "SELECT sql_calc_found_rows * FROM members".
" ORDER BY username LIMIT :startRow, :numRows";
try {
$st = $conn->prepare($sql);
$st->bindParam(":startRow", $startRow, PDO::PARAM_INT);
$st->bindParam(":numRows", $numRows, PDO::PARAM_INT);
$st->execute();
} catch (PDOException $e) {
die("Query failed: " . $e->getMessage());
}
Here I get error:
Query failed: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''5'' at line 1.
The LIMIT :startRow, :numRows has problem in :numRows.
I have tried both $st->bindParam and $st->bindValue, both didn't work.
I think the problem is with TBL_MEMBERS
I suppose this is a view(subselect).
So, if you have product table for example and you want to execute following statement:
select sql_calc_found_rows * from select id, code, name, slug, info from products order by code
you will receive following error:
SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select id, code, name, slug, info from products order by code' at line 1
But if you change query to:
select sql_calc_found_rows * from (select id, code, name, slug, info from products) v order by code
this will work.
To summarize,
TBL_MEMBERS is a view which should be put in parenthesis and given alias(i my example alias is 'v')
I recommend to look at the SQL query text what PDO actually produces. You can do this with help of MySQL's general query log.
Most probably, the formal types of $startRow and/or $numRows are strings, not integers, and resulting query is therefore something like LIMIT '0', '5' (syntax error) instead of LIMIT 0, 5 (correct).
The thing is, even with PDO::PARAM_INT, when the parameter's formal type is not integer (is_int returns false), PDO wraps it in quotes. So, you have to cast parameters to integers before binding them (e.g. using intval):
$st->bindParam(":startRow", intval(trim($startRow)), PDO::PARAM_INT);
$st->bindParam(":numRows", intval(trim($numRows)), PDO::PARAM_INT);
I solved it.I Type casted the :numRows placeholder.
$numRows=(int)$numRows;
$sql = 'SELECT sql_calc_found_rows * FROM ' .
TBL_MEMBERS .'ORDER BY'. $order .'LIMIT :startRow,:numRows';
try {
$st = $conn->prepare($sql);
$st->bindValue(":startRow", $startRow, PDO::PARAM_INT);
$st->bindValue(":numRows", $numRows, PDO::PARAM_INT);
$st->execute();
...
And it worked. I also noticed the ' should be use instead of ".
I'm having a strange issue running a query via PDO prepared statements that I just can't spot the issue. When running the query manually it works just fine. Here is the code (simplified for conciseness):
// Query Parameters
$params = array( 1, 5 );
// Get Products
$query = "SELECT * FROM mydb.Product
WHERE ProductId >= ?
AND IsApproved = 1
AND IsPublic = 1
LIMIT ?";
// Get Database Instance
$dbh = App\App::getDatabase()->getInstance();
// Prepare Query
if( $stmt = $dbh->prepare($query) )
{
if( $stmt->execute($params) )
{
// Return Records
}
}
Removing the LIMIT ? portion from the query returns all results as expected. Instead, when attempting to use the LIMIT and it passes 5 as the value, I get this error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''5'' at line 5
A dump of the PDOStatement object after preparation shows:
object(PDOStatement)[59]
public 'queryString' => string 'SELECT * FROM mydb.Product
WHERE ProductId >= ?
AND IsApproved = 1
AND IsPublic = 1
LIMIT ?'
I've tried putting a semicolon at the end of the query but that gives same error. Am I having cerebral flatulence and missing something obvious?
TL;DR; Why does my prepared statement fail when using the LIMIT clause with a 1064 error?
I think this could be a duplication of
PDO Mysql Syntax error 1064
The solution is to bind limit parameter forcing it to be an int instead of a string with a simple (int) cast.
just put (int) before your limit value