I'm working with PHP PDO and I have the following problem:
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in /var/www/site/classes/enterprise.php on line 63
Here is my code:
public function getCompaniesByCity(City $city, $options = null) {
$database = Connection::getConnection();
if(empty($options)) {
$statement = $database->prepare("SELECT * FROM `empresas` WHERE `empresas`.`cidades_codigo` = ?");
$statement->bindValue(1, $city->getId());
}
else {
$sql = "SELECT * FROM `empresas`
INNER JOIN `prods_empresas` ON `prods_empresas`.`empresas_codigo` = `empresas`.`codigo` WHERE ";
foreach($options as $option) {
$sql .= '`prods_empresas`.`produtos_codigo` = ? OR ';
}
$sql = substr($sql, 0, -4);
$sql .= ' AND `empresas`.`cidades_codigo` = ?';
$statement = $database->prepare($sql);
echo $sql;
foreach($options as $i => $option) {
$statement->bindValue($i + 1, $option->getId());
}
$statement->bindValue(count($options), $city->getId());
}
$statement->execute();
$objects = $statement->fetchAll(PDO::FETCH_OBJ);
$companies = array();
if(!empty($objects)) {
foreach($objects as $object) {
$data = array(
'id' => $object->codigo,
'name' => $object->nome,
'link' => $object->link,
'email' => $object->email,
'details' => $object->detalhes,
'logo' => $object->logo
);
$enterprise = new Enterprise($data);
array_push($companies, $enterprise);
}
return $companies;
}
}
It looks like you're trying to build a long(?) series of 'or' comparisons: if (x=1) or (x=2) or (x=3) etc.... You may find it easier to replace it with:
$cnt = count($options);
if ($cnt > 0) {
$placeholders = str_repeat(', ?', $cnt - 1);
$sql .= 'WHERE '`prods_empresas`.`produtos_codigo` IN (?' . $placeholders . ')';
}
which, if there were 5 options, would give you
WHERE prods_empresas.produtos_condigo IN (?, ?, ?, ?, ?)
And then do the values binding with:
$pos = 1;
foreach ($options as $option) {
$statement->bindValue($pos, $option->getId());
$pos++
}
You have a mismatch between the amount of bound parameters and the amount of binds in the SQL. Double check that the amount of ? and the amount of bound parameters is the same.
Additionally, HY093 will show up if you have tried to bind a parameter that does not exist:
$stmt = "INSERT INTO table VALUES (:some_value)";
$stmt->bindValue(':someValue', $someValue, PDO::PARAM_STR);
See that :some_value does not match :someValue! The fix is:
$stmt = "INSERT INTO table VALUES (:some_value)";
$stmt->bindValue(':some_value', $someValue, PDO::PARAM_STR);
Positional parameters in SQL start at 1. You're handling this by binding to position $i+1 in your $options loop.
But then you bind the last parameter for cidades_codigo to position count($options), which overwrites the last parameter set in the $options loop.
You need to bind the last parameter to position count($options)+1.
FWIW, you don't need to bindValue() at all. It's easier to just pass an array of parameters to execute(). Here's how I'd write this function:
public function getCompaniesByCity(City $city, $options = null) {
$database = Connection::getConnection();
$sql = "SELECT * FROM `empresas` WHERE `empresas`.`cidades_codigo` = ?"
$params = array();
$params[] = $city->getId();
if ($options) {
$sql .= " AND `prods_empresas`.`produtos_codigo` IN ("
. join(",", array_fill(1, count($options), "?") . ")";
foreach ((array)$options as $option) {
$params[] = $option->getId();
}
}
$statement = $database->prepare($sql);
echo $sql;
$statement->execute($params);
. . .
Also be sure to check the return value of prepare() and execute(), it will be false if there's an error, and you need to check for that and report the error. Or else enable PDO to throw exceptions on error.
I was running into this problem due to having extra entries in the named parameter mapping array passed to PDO::Statement->execute()
$args=array (":x" => 17 );
$pdo->prepare("insert into foo (x) values (:x)");
$pdo->execute($args); // success
$args[':irrelevant']=23;
$pdo->execute($args) // throws exception with HY093
Since you have made $i+1 in the loop so count($options) would equal the last $i+1 which makes a duplicate binding.Try
foreach($options as $i => $option)
{
$statement->bindValue($i + 1, $option->getId());
}
$statement->bindValue(count($options)+1, $city->getId());
Related
I tried what I could to solve this error without any success.
I'm trying to dynamically build a select query with prepared statement :
I've got this code
$gm = $_GET['gm'];
$ct = $_GET['ct'];
$query = 'SELECT * FROM fchar';
$cond = array();
$params = array();
$type = array();
if (!empty($gm)) {
$cond[] = "gm = ?";
$params[] = $gm;
$type[] = "i";
}
if (!empty($ct)) {
$cond[] = "ct = ?";
$params[] = $ct;
$type[] = "s";
}
if (count($cond)) {
$query .= ' WHERE ' . implode(' AND ', $cond);
}
if (count($params)) {
$paramok = implode(', ', $params);
}
if (count($type)) {
$typeok = implode($type);
}
$stmt = $connection->prepare($query);
$stmt->bind_param(''.$typeok.'',$paramok);
$stmt->execute();
if (!$stmt->execute()) {
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
while ($stmt->fetch()) {
}
I've got this error :
Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables in C:\xampp\htdocs\cebusingles\search.php on line 42
But when I echo the types and params, I've got this :
echo "<br><br>Types : ".$typeok."<br>Param: ".$paramok."<br><br>Query: ".$query."<br><br>";
// gives :
// Types : is
// Param: 1, USA
// Query: SELECT * FROM fchar WHERE gm = ? AND ct = ?
So I have 2 types : is, and 2 param : 1, USA.
I don't understand why it says that the number of types is not matching the number of params.
$stmt->bind_param(''.$typeok.'',$paramok);
Is missing what type of variabled you're trying to insert on the ? places
$stmt->bind_param('ss', ''.$typeok.'', $paramok);
You can read here what characters should be used when using numbers, strings or such:
http://php.net/manual/en/mysqli-stmt.bind-param.php
types
A string that contains one or more characters which specify the types
for the corresponding bind variables:
I'm attempting to write a parameterized query with PDO that accepts a number of inputs and acts as a search on a specific table.
There are a number of columns I wish to search on but each of which could be optional. The query simplified might look like:
SELECT * FROM item
WHERE name LIKE '%?%'
AND col2 IN (?, ?, .......)
AND col3 IN (?, ?, .......)
...
and with the IN clause, there could be any number (0 or more) of terms for each column.
Since the IN clause won't work with 0 values, and in PDO I'd have to iterate over each array passed in for each IN clause - I'm wondering if there's a better way to structure this as it seems like a big mess.
You can use "call_user_func_array" to make it dynamic. This is what I use:
public function selectMSData($handlename, $sql, $type='', $params = array())
{
if ( ! is_string($sql) || ! is_array($params) ) {
die('wrong param types');
}
$this->dbconnect($handlename); //connect to db and save connection with handle-name
$result = array();
$aRows = 0;
if(sizeof($params)==0) {
//simple query without runtime parameters
$msres = $this->dbhandle[$handlename]->query($sql);
if($msres === false) {
//log error
} else {
while($mres = $msres->fetch_array(MYSQLI_ASSOC)) {
$aRows++;
$result[] = $mres;
}
}
} else {
//prepared statement using runtime parameters
$stmt = $this->dbhandle[$handlename]->prepare($sql);
if(!$stmt) {
//log error
}
$valArr = array();
$valArr[] = $type;
foreach($params as $pkey => $pval) {
$valArr[] = &$params[$pkey];
}
call_user_func_array(array(&$stmt, 'bind_param'), $valArr);
if(!$stmt->execute()) {
//log error
};
$stmt->store_result(); //fetch is super-slow for text-fields if you don't buffer the result!!
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$resfields[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $resfields);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$result[] = $c;
$aRows++;
}
$stmt->close();
}
$this->result = $result;
return $aRows;
}
and you call it like this:
$db->selectMSData('my_db_name', 'SELECT * FROM example WHERE a=? AND b=? LIMIT 1', 'ss', array($a, $b));
I am trying to build a class to select records from table defined on user input.
my plan is to make a class that read user input conditions as an array, because there is a lot of other forms for other tables with different number of conditions. therefore I want to make one class that works with all.
basically am trying to achieve this statement:
select * from table_name where condition1=value1 AND condition2=value2
where the number of conditions varies depends on user input.
what I have done is this class:
class SelectQuery {
private $Error;
public $conn;
public $table;
public function __construct($conn){ //establish connection
$this->conn = $conn;
}
public function SelectData($table,$cols,array $conds){
$this->table = $table;
if($cols)
{
if($conds)
{
$i=0; $cond='';
foreach ($conds as $key => $value){
if($i==0)
{
$cond .= $key. '=' .$value;
}
else
{
$cond .= ' AND ' .$key. '=' .$value;
}
$i++;
}
$sql = 'SELECT '.$cols.' FROM '.$table.' WHERE '.$cond.'';
$stmt = oci_parse($this->conn, $sql);
oci_execute($stmt);
$rows = oci_fetch_all($stmt, $data, null, null, OCI_FETCHSTATEMENT_BY_COLUMN);
if($rows)
{
return $rows['0']['0'];
}
else
{
return NULL;
}
}
else
{
$sql = 'SELECT {$cols} FROM {$table}';
$stmt = oci_parse($this->conn, $sql);
oci_execute($stmt);
$rows = oci_fetch_all($stmt, $data, null, null, OCI_FETCHSTATEMENT_BY_COLUMN);
if($rows)
{
return $rows['0']['0'];
}
else
{
return NULL;
}
}
}
}
and this is how the data is sent from form:
$manager = new OracleConnectManager();
$conn = $manager->establishConnection(); //establish connection
if( $conn ){
$table = 'MAIN';
$cols= '*';
$conds = array(
'id_number' => $_POST['id_number'],
'firstname' => $_POST['firstname'],
'secondname' => $_POST['secondname'],
'thirdname' => $_POST['thirdname'],
'familyname' => $_POST['familyname'],
'department' => $_POST['department'],
);
$sel = new SelectQuery($conn);
$dth = $sel->SelectData($table,$cols,$conds);
echo $dth;
}
when I run the code i get this error:
Warning: oci_execute(): ORA-00936: missing expression
(in the SelectQuery class, line: oci_execute($stmt); )
Warning: oci_fetch_all(): ORA-24374: define not done before fetch or execute and fetch
(in the SelectQuery class, line: $rows = oci_fetch_all($stmt, $data, null, null, OCI_FETCHSTATEMENT_BY_COLUMN); )
and there is no output.
any help would be appreciated.
thanks
There are two things:
You need to validate all the $_POST values and build $conds array for values that are set in $_POST.
Update:
i.e. if you have only one or two post values then the $conds array should be:
$conds = array(
'id_number' => $_POST['id_number'],
'firstname' => $_POST['firstname']
)
In your foreach loop, $value needs to be quoted if it is varchar type as:
foreach ($conds as $key => $value){
if($i==0)
{
$cond .= "$key='$value'";
}
else
{
$cond .= " AND $key='$value'";
}
$i++;
}
Update:
if you have int and varchar fields then do formatting when building array itself:
$conds = array(
'id_number' => $_POST['id_number'], // int type
'firstname' => "'".$_POST['firstname']."'" // string type
)
else
{
$sql = 'SELECT ' . $cols . ' FROM ' . $table;
// Other Statements
}
Hope the ELSE part for $sql should be framed like above?
So, I am passing arrays of values that will vary upon use into a method that then inserts them into a database. My problem is the way in which the parameters are bound.
public function insertValues($table, $cols, $values)
{
$mysqli = new mysqli(DBHOST, DBUSER, DBPASSWORD, DBDATABASE);
$colString = implode(', ', $cols); // x, x, x
$valString = implode(', ', array_fill(0, count($values), '?')); // ?, ?, ?
$sql = "INSERT INTO $table ($colString) VALUES($valString)";
if (!$stmt = $mysqli->prepare($sql))
echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
// THIS IS THE PROBLEM AREA
foreach ($values as $v)
if (!$stmt->bind_param('s', $v))
echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
if (!$stmt->execute())
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
$stmt->close();
$mysqli->close();
}
I need a way to bind all the parameters at once I think, and not one at a time, but I can't figure out a useful way to do this. Any help would be greatly appreciated.
I found the answer for the problem you are looking for on PHP.net (http://php.net/manual/en/mysqli-stmt.bind-param.php). I'm pasting it here for convenience, all credit goes to a man going by the email Nick9v ^ät^ hotmail -remove- -dot- com
When dealing with a dynamic number of field values while preparing a
statement I find this class useful.
[Editor's note: changed BindParam::add() to accept $value by reference
and thereby prevent a warning in newer versions of PHP.]
<?php
class BindParam{
private $values = array(), $types = '';
public function add( $type, &$value ){
$this->values[] = $value;
$this->types .= $type;
}
public function get(){
return array_merge(array($this->types), $this->values);
}
}
?>
Usage is pretty simple. Create an instance and use the add method to populate. When you're ready to execute simply use the get method.
<?php
$bindParam = new BindParam();
$qArray = array();
$use_part_1 = 1;
$use_part_2 = 1;
$use_part_3 = 1;
$query = 'SELECT * FROM users WHERE ';
if($use_part_1){
$qArray[] = 'hair_color = ?';
$bindParam->add('s', 'red');
}
if($use_part_2){
$qArray[] = 'age = ?';
$bindParam->add('i', 25);
}
if($use_part_3){
$qArray[] = 'balance = ?';
$bindParam->add('d', 50.00);
}
$query .= implode(' OR ', $qArray);
//call_user_func_array( array($stm, 'bind_param'), $bindParam->get());
echo $query . '<br/>';
var_dump($bindParam->get());
?>
This gets you the result that looks something like this:
SELECT * FROM users WHERE hair_color = ? OR age = ? OR balance = ?
array(4) { [0]=> string(3) "sid" 1=> string(3) "red" [2]=> int(25) [3]=> float(50) }
The code doesn't work because bind_param has to have all of the query parameters in a single call to the function instead of multiple calls for each parameter, also it needs variables passed by reference, so in the foreach call it would always be the same variable with the value that it had in the last iteration of the loop.
The easiest way would be to compose an array with the types and parameters, and then pass it to bind_param with a call to call_user_func_array, for example:
$params = array();
$types = '';
foreach ($values as $k => $v)
{
$types .= 's';
$params[] = &$values[$k];
}
$bind_params = array_merge(array($types), $params);
if (!call_user_func_array(array($stmt, 'bind_param'), $bind_params))
// ...
Note that bind_param expects variables to be passed by reference not by value, otherwise it would be a couple of lines constructing an array with values, instead of the foreach loop.
I have been trying to learn prepared statements so that we can start implementing them thoughout our PHP sites. This function takes values (or none) from text boxes on a search form using the $_POST transfer method then uses the names and values of those textboxes to add criteria to the WHERE clause. The function worked previously ut I can't seem to get the prepared statement to function.
Researching several scripts I started using the one below and worked out a few bugs. now when I run it I get the error Wrong parameter count for mysqli_stmt::bind_param()
After this query runs I want to export the values into a table and was working before attempting the prepared statement.
Here is the code I have so far:
<?php
$db = mysqli_connec("ip_address", "loginname", "password", "database");
$refs = array('sssss');
foreach ($_POST as $key => $value)
{
$refs[] =& $_POST[$key];
}
$query = "SELECT col1, col2, col3, col4, col5 FROM tbl_name WHERE 1=1";
foreach ($_POST as $k => $v)
{
if(!empty($v)) {
$query .= " AND $k = ?";
$params[$k] = $v;
}
}
$results = $db->prepare($query);
call_user_func_array(array($results, 'bind_param'), $refs);
$results->execute();
?>
Parameters passed to mysqli_stmt::bind_param must be passed by reference, and only variables can be passed by reference, so you cannot pass the result of a function directly into call_user_func_array in this case. Also, the first parameter passed to bind_param is a string list of the variable types. Instead, try this:
// Change to whatever types are relevant
$refs = array('sss');
foreach ($arr as $key => $value)
{
$refs[] =& $arr[$key];
}
call_user_func_array(array($results, 'bind_param'), $refs);
Edit
This line is wrong:
$results = $mysqli->prepare($query);
Should be
$results = $db->prepare($query);
Ok, the way I solved this was to scrap everything and start over, which is always fun...but the new script that works for what I need is here
<?php
$dbhost = "ip_address";
$dbname = "db_name";
$dbuser = "db_login";
$dbpass = "db_pass";
$conn = new PDO("mysql:host=$dbhost;dbname=$dbname",$dbuser,$dbpass);
$query = "SELECT * FROM tbl_name WHERE 1=1";
foreach ($_POST as $k => $v)
{
if(!empty($v)) {
$query .= " AND $k LIKE ?";
$params[] = $v;
}
}
$results = $conn->prepare($query);
$results->execute($params);
$results->bindColumn(1, $no);
$results->bindColumn(2, $date);
$results->bindColumn(3, $name);
$results->bindColumn(4, $id);
$results->bindColumn(5, $path);
?>