45
// BUILD VALUES
$count = count($matches);
for($i = 0; $i < $count; ++$i) {
    $values[] = '(?)';
}
// INSERT INTO DATABASE
$q = $this->dbc->prepare("INSERT INTO hashes (hash) VALUES " . implode(', ', $values) . " ON DUPLICATE KEY UPDATE hash = hash");
$q->execute($matches);

The code above fails with the following error

SQLSTATE[HY093]: Invalid parameter number: parameter was not defined

Although when count($matches) == count($values) just before execute is called?

What is going on here?

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Griff
  • 1,647
  • 2
  • 17
  • 27
  • 9
    Have you tried passing array_values($matches) instead? – Ja͢ck Jun 10 '12 at 04:40
  • Why `ON DUPLICATE KEY UPDATE hash = hash`? May as well do `INSERT IGNORE ...`. Did you mean to do `UPDATE hash = VALUES(hash)`? – eggyal Jun 10 '12 at 05:12
  • 1
    Did the `$values` array already contain something prior to the loop? Also, why not prepare a single `INSERT INTO hashes (hash) VALUES (?)` and execute it multiple times? – eggyal Jun 10 '12 at 05:18
  • @eggyal I am inserting around 1000 values and doing a multiple insert is around 10 times faster :) – Griff Jun 10 '12 at 16:09
  • @Griff: Is that even true in the case of executing a prepared statement multiple times? I've never benchmarked it myself, but had always understood the performance impact to be negligible... – eggyal Jun 10 '12 at 21:45
  • @eggyal Well I would guess so making one query or 1000 I can almost gurantee that 1 would be faster. – Griff Jun 10 '12 at 23:40
  • I just ran into this and for anyone else who might have the same root cause, I'd like to explain why the solution proposed by @Ja͢ck works: PDO's execute, when given an argument, will bind the values in the argument array on its own. If the argument is a numeric array, this will only work with ? placeholders, but if it's an associative array, it will only work with named placeholders because PDO uses the array's keys to bind the parameters. In the example, ? placeholders are used, and array_values($matches) will rebase the array into a numeric array, removing any string keys. – scenia Oct 16 '20 at 08:51

5 Answers5

53

This error you are receiving is because the number of elements in $values & $matches does not match.

If $values & $matches do not contain the same number of elements then the insert will fail, due to the query expecting X params but it is receiving Y data $matches. In your case, $values probably already contains some values, which is the reason for the count mismatch. To avoid that, you must always initialize an array before the loop.

I believe you will also need to ensure the column hash has a unique index on it as well.

$matches = array('1');
$count = count($matches);
$values = [];
for($i = 0; $i < $count; ++$i) {
    $values[] = '(?)';
}

// INSERT INTO DATABASE
$sql = "INSERT INTO hashes (hash) VALUES " . implode(', ', $values) . " ON DUPLICATE KEY UPDATE hash=values(hash)";
$stmt = $dbh->prepare($sql);
$data = $stmt->execute($matches);
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Haroon
  • 1,125
  • 1
  • 11
  • 13
  • 4
    Great answer, I just ran into this, because i defined two columns with the same identifier - `:example`, that should also be a point to check for! – Darren Jun 22 '15 at 03:12
  • 1
    Just to Add, it might be because the parameter that you are trying to bind simply does not exists... – Haine Dec 03 '15 at 03:41
  • @Darren - You are my live saver. In my bindParam call that was executed in a foreach loop I had forgotten to include the keyfrom teh array index so I got bindParam(':id', 'someValue'), bindParam(':id', 'someValue2'),... instead of bindParam(':id0', 'someValue'), bindParam(':id1', 'someValue'),... Hope this helps someone to solve his mistery – Erik Čerpnjak May 31 '16 at 22:11
  • 1
    I think you misunderstood the OP's intent. He was imploding `$count` times `(?)` -and not just `?`- with `,` because something like `INSERT INTO hashes(hash) VALUES('What'),('Terrible'),('Failure');` would work in SQL to make 3 row insertions in one statement. Your answer suggest something like `INSERT INTO hashes(hash) VALUES('What', 'Terrible' , 'Failure');` -referencing one column and give 3 values for a row! That would throw `Column count doesn't match value count` error- **How could this get +45 upvotes and OP's approval?** (Of course: "You will need to adapt it a little." you said...) – SebasSBM Sep 17 '19 at 09:31
  • 1
    @SebasSBM this was exactly my thought. I editied it out, to make it at least sensible. Going into my collection of Stack Overflow WTFs. SO voting is *such* a mystery. – Your Common Sense Apr 30 '22 at 06:24
10

SQLSTATE[HY093]: Invalid parameter number: parameter was not defined

Unfortunately this error is not descriptive for a range of different problems related to the same issue - a binding error. It also does not specify where the error is, and so your problem is not necessarily in the execution, but the sql statement that was already 'prepared'.

These are the possible errors and their solutions:

  1. There is a parameter mismatch - the number of fields does not match the parameters that have been bound. Watch out for arrays in arrays. To double check - use var_dump($var). "print_r" doesn't necessarily show you if the index in an array is another array (if the array has one value in it), whereas var_dump will.

  2. You have tried to bind using the same binding value, for example: ":hash" and ":hash". Every index has to be unique, even if logically it makes sense to use the same for two different parts, even if it's the same value. (it's similar to a constant but more like a placeholder)

  3. If you're binding more than one value in a statement (as is often the case with an "INSERT"), you need to bindParam and then bindValue to the parameters. The process here is to bind the parameters to the fields, and then bind the values to the parameters.

     // Code snippet
     $column_names = array();
     $stmt->bindParam(':'.$i, $column_names[$i], $param_type);
     $stmt->bindValue(':'.$i, $values[$i], $param_type);
     $i++;
     //.....
    
  4. When you're inconsistent in using The backtick to delimit literals that represent identifiers (not strings). (Using ``), but once you use them you have to be consistent for that query, i.e. you can't use backticks for one identifier and not use them for another, all have to have backticks if you use them. (e.g. SELECT id FROM my_table) Do not use back-ticks for placeholders

  5. Any value in '' single quotes is always treated as a string literal and will not be read as a column/table name or placeholder to bind to.

Danny F
  • 392
  • 8
  • 21
  • 1
    Thanks for having reason #2, got be flabbergasted till I found this here. –  Mar 10 '18 at 01:24
  • And your initial version was also wrong. You **cannot** bind values to [column_names](https://stackoverflow.com/a/182353/285587) – Your Common Sense May 01 '22 at 14:36
  • In case you think you can do something like this WHERE ` :column ` =1 , [it won't work](https://phpize.online/sql/mysql57/undefined/php/php8/66f82f2dafba85f84542f3b250543de6/). Notice the double '' in the error message around id. PDO adds quotes to values, making the filed name literally `'id'`. Which means you cannot use placeholders for column names. Backticks have absolutely nothing to do with placeholders and cannot be used with placeholders in any way, either as a part of the name or surrounding. Please don't spread misleading information. – Your Common Sense May 02 '22 at 04:25
  • Dunno where did you get it but [you can use backticks inconsistently, wherever you want](https://phpize.online/sql/mysql57/undefined/php/php8/34c043edbd1bd3af0196afbdb41e28bd/) even for one single column name. Besides, all this backtick business has absolutely nothing to do with **question you are trying to answer**. The question is about placeholders, not backticks. – Your Common Sense May 02 '22 at 16:29
  • And I just noticed, #3 also makes no sense. As we learned already, no column name can be bound, so it's hard to tell what are you talking about. Let alone there is no such problem that can be solved by interlacing bindParam and bindValue. You can use either or both. And neither would cause the invalid parameter number error. The entire "to bind the parameters to the fields, and then bind the values to the parameters" statement is a crystal clear nonsense. – Your Common Sense May 02 '22 at 16:38
  • @YourCommonSense While I do value your input, I find your approach condascending. I'm here to learn, and I don't claim to be *the* standard repository of all knowledge and wisdom. I feel that labelling everything I write as nonsense is counter productive to the purpose of SO. Having dealt with this error in various versions of PHP, I have found that using back ticks inconsistently with PDO and placeholders (i.e. using backticks where appropriate but nonetheless inconsistently) has resulted in this error. Perhaps I'm wrong. Perhaps not. I'm speaking from my experience. – Danny F May 06 '22 at 14:48
  • 1
    __6. There is a dash in a variable name__. E.g. column-a = :column-a Which may be set while the automated variable name is created according to the silly column name where dash is used. – Jirka Kopřiva Sep 23 '22 at 19:07
3

I had the same error after using the wrong parameter name when binding.

Notice :tokenHash in the VALUES clause of the query, but :token_hash when binding.

Fixing one or the other resolved the error.

// Prepare DB connection
$sql = 'INSERT INTO rememberedlogins (token_hash,user_id,expires_at)
        VALUES (:tokenHash,:user_id,:expires_at)';
$db = static::getDB();
$stmt = $db->prepare($sql);

// Bind values
$stmt->bindValue(':token_hash',$hashed_token,PDO::PARAM_STR);
Hannah
  • 106
  • 1
  • 6
-1

The same error I found will show if you have a mismatch of the column name in PHP & the database column name, Double check that too. This is what I had wrong.

Spinstaz
  • 287
  • 6
  • 12
-2

I understand that the answer was useful however for some reason it does not work for me however I have moved the situation with the following code and it is perfect

    <?php

$codigoarticulo = $_POST['codigoarticulo'];
$nombrearticulo = $_POST['nombrearticulo'];
$seccion        = $_POST['seccion'];
$precio         = $_POST['precio'];
$fecha          = $_POST['fecha'];
$importado      = $_POST['importado'];
$paisdeorigen   = $_POST['paisdeorigen'];
try {

  $server = 'mysql: host=localhost; dbname=usuarios';
  $user   = 'root';
  $pass   = '';
  $base   = new PDO($server, $user, $pass);

  $base->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $base->query("SET character_set_results = 'utf8',
                     character_set_client = 'utf8',
                     character_set_connection = 'utf8',
                     character_set_database = 'utf8',
                     character_set_server = 'utf8'");

  $base->exec("SET character_set_results = 'utf8',
                     character_set_client = 'utf8',
                     character_set_connection = 'utf8',
                     character_set_database = 'utf8',
                     character_set_server = 'utf8'");

  $sql = "
  INSERT INTO productos
  (CÓDIGOARTÍCULO, NOMBREARTÍCULO, SECCIÓN, PRECIO, FECHA, IMPORTADO, PAÍSDEORIGEN)
  VALUES
  (:c_art, :n_art, :sec, :pre, :fecha_art, :import, :p_orig)";
// SE ejecuta la consulta ben prepare
  $result = $base->prepare($sql);
//  se pasan por parametros aqui
  $result->bindParam(':c_art', $codigoarticulo);
  $result->bindParam(':n_art', $nombrearticulo);
  $result->bindParam(':sec', $seccion);
  $result->bindParam(':pre', $precio);
  $result->bindParam(':fecha_art', $fecha);
  $result->bindParam(':import', $importado);
  $result->bindParam(':p_orig', $paisdeorigen);
  $result->execute();
  echo 'Articulo agregado';
} catch (Exception $e) {

  echo 'Error';
  echo $e->getMessage();
} finally {

}

?>
caleman9791
  • 1
  • 1
  • 1