Can someone help me get the SELECT query (2). below to work?
This string used for both SELECT statements:
$seller_items = ('6','9','12','13','14','15','16','17','18','19','20','22','23','24','25','26','28','27','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','53','54','55','57','58','59','60','62','63','64','65','61','67','56','69','70','74','73','75','78','80','76','72','95','94','101','102','71','103','2','104','4','81','21','10','11','3','79','5','8','7','97','93','96','98');
(1). This SELECT query is working fine:
if ($stmt = $mysqli->prepare("SELECT info FROM items WHERE item_id IN $seller_items AND active = ?")){
$stmt->bind_param("s",$active);
(2). This SELECT query is not working:
if ($stmt = $mysqli->prepare("SELECT info FROM items WHERE item_id IN ? AND active = ?")){
$stmt->bind_param("ss",$seller_items,$active);
I think placing the variable in the SELECT query itself may defeat the purpose of a prepared statement.
I can get the IN predicate to work just fine with a non-prepared statement. It's the prepared statement with which I am having the problem.
Thank you in advance.
As #Dai mentioned the IN cannot be parameterized with just one variable. Sure it can be done with a series of parameters but the number of them is fixed. The idea with prepare statements is that the insertion of values are expected the same position, the same number of parameters and the same kind.
If the amount of parameter inside of the IN is fixed, something like this works:
$a=[1,2,3];
$s=$mysqli->prepare("SELECT id FROM users WHERE role_id IN (?,?,?)");
$s->bind_param('iii',$a[0],$a[1],$a[2]);
$s->execute();
$s->bind_result($id);
$c=[];
while($s->fetch()){
$c[]=$id;
}
var_dump($c);
Maybe this is not the answer that you are looking for, but if the amount of variables is not know better insert the imploded array string inside the original SQL command.
$a=[1,2,3];
$b="('".implode("','",$a)."')";
$s=$mysqli->prepare("SELECT id FROM users WHERE role_id IN {$b}");
$s->execute();
$s->bind_result($id);
$c=[];
while($s->fetch()){
$c[]=$id;
}
var_dump($c);
Related
I’ve been trying to code a login form in PHP using a prepared statement but every time I try to log in I get the following error:
mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement
Here is my code:
<?php
$mysqli = new mysqli("localhost", "root" , "" , "security");
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password, $username);
$stmt->fetch();
Can someone tell me why this is happening?
$mysqli->prepare("SELECT username, password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s' ,$username);
$stmt->execute();
$stmt->bind_result($username, $password);
Your select syntax was wrong, the correct syntax is
SELECT field1, field2, field3 FROM TABLE WHERE field1 = ? AND field2 = ?
To select more fields simply separate them by a comma and not an AND
Explanation
The error message clearly states that the number of columns you are SELECTing does not match the number of variables you provided to mysqli_stmt::bind_result(). They need to match exactly.
For example:
-- ↓ 1 ↓ 2 ↓ 3
SELECT col1, col2, col3 FROM tableA
There are 3 columns being fetched, so you need to provide 3 variables.
$stmt->bind_result($var1, $var2, $var3);
There could be a number of reasons why the column count doesn't match variable count.
Count your columns and variables
The simplest cause is that you made a mistake in the count. Do a recount of both. Maybe you changed the SQL but forgot to adjust bind_result()?
SELECT *
Using SELECT * is not recommended with bind_result(). The number of columns in the table could change as a result of schema changes or joins and will break your application. Always list all the columns explicitly!
Logical problem with SQL
The code from the question contains a logical mistake. SELECT username AND password produces a single column in the result. The AND keyword evaluates to a boolean expression. To select multiple columns you must use ,. Maybe there is another logical error in the query that causes the SQL to produce a different number of columns than you expected?
UPDATE and INSERT
DML statements such as INSERT, UPDATE and DELETE do not produce result sets. You can't bind variables to such prepared statement. You need to execute another SELECT statement to fetch the data.
Fetching an array from the prepared statement
The return value of mysqli_stmt::bind_result() is not an array, it's just a boolean. If you expected this function to return an array, then you are probably looking for get_result() with fetch_all() instead.
To select an array you need to get the mysqli_result object first.
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$mysqli_result = $stmt->get_result();
// The object can then be iterated or used with fetch_* methods
foreach($mysqli_result as $row) {
}
// or
$arrayRow = $mysqli_result->fetch_assoc();
If this function doesn't exist in your PHP installation, then it means you have PHP not installed properly. You need to either recompile it, or enable mysqlnd (e.g. in cPanel).
If you are only learning PHP, it would be much easier for you to learn PDO instead.
From experience and also having been told constantly the benefits of using prepared statements and binding my parameters, I have constantly used those two techniques in my code, however I would like to understand exactly the purpose of each of those two techiques:
From my understanding of prepared statements:
$sql = "SELECT * FROM myTable WHERE id = ".$id;
$stmt = $conn->prepare($sql);
$stmt->execute();
The previous code should create a sort of a buffer in the database with the query I proposed. Now FROM MY UNDERSTANDING (and I could be very wrong), the previous code is insecure, because the string $sql could be anything depending on what $id actually is, and if $id = 1; DROP TABLE myTable;--, I would be inserting a malicious query even though I have a prepared statement.
FROM MY UNDERSTANDING this is where binding my parameters com in. If I do the following instead:
$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $id);
$stmt->execute();
The database should know exactly all the parts of the sql statement before hand:
SELECT these columns: * FROM myTable and WHERE id = "a variable that was input by the user", and if "a variable that was input by the user" != a variable, the query fails.
I have been told by some my understanding is correct, and by others that it is false, could someone please let me know if I am wrong, correct, or missing something? And elaborate as much as you want, all feedback is greatly appreciated!
You're correct that the first case is insecure. It's important to understand though, that preparing a statement only has value if you are using variable data, and/or executing the same query repeatedly. If you are executing plain statements with no variables, you could simply do this:
$sql = "SELECT * from myTable WHERE this_column IS NOT NULL";
$result = $conn->query($sql);
And end up with a PDOStatement object to work with, just like when you use PDO::exec().
For your second case, again, you're largely correct. What's happening is the variable passed to the database is escaped and quoted (unless you specify otherwise with the third argument to PDOStatement::bindParam(), it's sent as a string which is fine for most cases.) So, the query won't "fail" if bad data is sent. It behaves exactly as if you had passed a valid number that didn't exist as an ID in the database. There are, of course, some edge cases where you are still vulnerable even with a correctly prepared statement.
Also, to make life easier, you can use prepared statements like this, to do implicit binding:
$sql = "SELECT * FROM myTable WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->execute([":id"=>$id]);
Or even like this, with un-named parameters:
$sql = "SELECT * FROM myTable WHERE id = ?";
$stmt = $conn->prepare($sql);
$stmt->execute([$id]);
Naturally, most of this has been explained in the comments while I was typing up the answer!
So, I'm working on some PHP and using prepared statements to access my database with mysqli, the following statement has a different outcome when prepared statements are used than if I
run the query using HeidiSQL, manually inserting the values
run the query without using prepared statements and manually entering the values
Prepared statement query:
$qStr = "SELECT id, head, descrip, img.dir
FROM CS_P, img,Q_Q_Items
WHERE CS_P.id = Q_Q_Items.I_ID
AND CS_P.id = img.Product_ID
AND img.priority=1
AND CS_P.id NOT IN(
SELECT ID2
FROM I_Cols, Q_Q_Items
WHERE ID2 = Q_Q_Items.I_ID
AND Q_Q_Items.Q_ID = ?
AND ID1 IN (".$madeMarks.")
)
AND Q_Q_Items.Q_ID = ?;";
Manually entered query:
SELECT id, head, descrip, img.dir
FROM CS_P, img,Q_Q_Items
WHERE CS_P.id = Q_Q_Items.I_ID
AND CS_P.id = img.Product_ID
AND img.priority=1
AND CS_P.id NOT IN(
SELECT ID2
FROM I_Cols, Q_Q_Items
WHERE ID2 = Q_Q_Items.I_ID
AND Q_Q_Items.Q_ID = 2
AND ID1 IN (35)
)
AND Q_Q_Items.Q_ID = 2;
The difference:
This statement is intended to omit rows where the value of ID_Cols.ID1 matches one of the values to be bound in place of $madeMarks. $mademarks is just a string of question marks that are inserted into the string so that I can insert numeric values that are posted to the PHP using AJAX and correspond to choices the user has previously made.
When I run this query on the SQL server directly it correctly omits rows where ID1 is in the values entered in place of $madeMarks in the prepared statement, with prepared statements these rows are always included and they shouldn't be as this means a previous choice made by the user clashes with this choice meaning it should not be displayed.
Is the way prepared statements are processed making this query impossible to be executed as intended?
Further information:
I have echoed out the values and they are correct integers, no strings or characters and all of the data is correct before it is bound to the prepared statement, I have used intval to make sure of this.
JSON encoded array of values bound to the query:
[2,35,2]
Bind type string:
'iii'
$madeMarks:
'?'
I have bound the values to the statement with 'i' binds as the database is expecting.
ID1 and ID2 are integers.
//////////////////////////////////////////////////////////////////////
It is now functional.
This isn't really an answer but is is a workaround, I just used preg_replace to replace any non-numeric chars with empty characters and then built the in statement with a for loop, inserting values from the choices array into the statement. There is no vulnerability to SQL injection as it is only numeric characters, this would be vulnerable to injection if it was a string that I had to compare.
Do not do this, it is hackish and bad, just use PDO.
I have a conventional query that works just fine that looks like this:
$result = $mysqli->query("SELECT value FROM activities WHERE name = 'Drywall'");
This succeeds in returning a row. However, for the purposes of diagnosing the problem I'm having with a prepared statement, I tried an identical query as a prepared statement like so:
$stmt = $mysqli->prepare("SELECT value FROM activities WHERE name = 'Drywall'");
$stmt->execute();
Despite the fact these are identical query strings, $stmt->num_rows is always 0. Why would the conventional query work, but the prepared statement not when they are the same exact query? Also, I realize including 'Drywall' in the prepared query string runs counter to the purpose of prepared statements, but I was just trying to eliminate the possibility that bind_param() was the culprit. So I was using bind_param() to fill in placeholders and that wasn't working either, despite my double-checking at runtime that the variable I was binding contained the correct value.
I think you want to use
$stmt->store_result();
before the call
$stmt->num_rows();
see last line of the descripton in the manual for $stmt->num_rows() (http://www.php.net/manual/en/mysqli-stmt.num-rows.php).
Check for proper use of the mysqli->prepare. The function depends on a parameter to be passed. It is different from passing the values directly in the query but can use with another way.
Verify the manual:
http://www.php.net/manual/pt_BR/mysqli.prepare.php
Did you try something like this:
$stmt = $mysqli->prepare("SELECT value FROM activities WHERE name = 'Drywall'");
$stmt->execute();
$res = $stmt->get_result();
$row = $res->fetch_assoc();
PS:
Prepared statements are Good. I would urge you to ALWAYS consider using them.
But in this case, a simple query would be much more efficient (would incur fewer round trips) than a prepared statement.
I’ve been trying to code a login form in PHP using a prepared statement but every time I try to log in I get the following error:
mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement
Here is my code:
<?php
$mysqli = new mysqli("localhost", "root" , "" , "security");
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password, $username);
$stmt->fetch();
Can someone tell me why this is happening?
$mysqli->prepare("SELECT username, password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s' ,$username);
$stmt->execute();
$stmt->bind_result($username, $password);
Your select syntax was wrong, the correct syntax is
SELECT field1, field2, field3 FROM TABLE WHERE field1 = ? AND field2 = ?
To select more fields simply separate them by a comma and not an AND
Explanation
The error message clearly states that the number of columns you are SELECTing does not match the number of variables you provided to mysqli_stmt::bind_result(). They need to match exactly.
For example:
-- ↓ 1 ↓ 2 ↓ 3
SELECT col1, col2, col3 FROM tableA
There are 3 columns being fetched, so you need to provide 3 variables.
$stmt->bind_result($var1, $var2, $var3);
There could be a number of reasons why the column count doesn't match variable count.
Count your columns and variables
The simplest cause is that you made a mistake in the count. Do a recount of both. Maybe you changed the SQL but forgot to adjust bind_result()?
SELECT *
Using SELECT * is not recommended with bind_result(). The number of columns in the table could change as a result of schema changes or joins and will break your application. Always list all the columns explicitly!
Logical problem with SQL
The code from the question contains a logical mistake. SELECT username AND password produces a single column in the result. The AND keyword evaluates to a boolean expression. To select multiple columns you must use ,. Maybe there is another logical error in the query that causes the SQL to produce a different number of columns than you expected?
UPDATE and INSERT
DML statements such as INSERT, UPDATE and DELETE do not produce result sets. You can't bind variables to such prepared statement. You need to execute another SELECT statement to fetch the data.
Fetching an array from the prepared statement
The return value of mysqli_stmt::bind_result() is not an array, it's just a boolean. If you expected this function to return an array, then you are probably looking for get_result() with fetch_all() instead.
To select an array you need to get the mysqli_result object first.
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$mysqli_result = $stmt->get_result();
// The object can then be iterated or used with fetch_* methods
foreach($mysqli_result as $row) {
}
// or
$arrayRow = $mysqli_result->fetch_assoc();
If this function doesn't exist in your PHP installation, then it means you have PHP not installed properly. You need to either recompile it, or enable mysqlnd (e.g. in cPanel).
If you are only learning PHP, it would be much easier for you to learn PDO instead.