How to fix CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters in MySQL

MySQLINTERMEDIATEHIGHUpdated Dec 29, 2025ยท 220 views

This MySQL client error occurs when a prepared statement is executed without binding values to all the parameter placeholders (?) defined in the SQL query. The error indicates a mismatch between the expected parameters and the data actually provided.

Quick fix

Fixes 2031

#include <mysql.h>
#include <string.h>

MYSQL_STMT *stmt = mysql_stmt_init(mysql);
if (!stmt) {
    fprintf(stderr, "mysql_stmt_init() failed\n");
    return 1;
}

const char *query = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, query, strlen(query))) {
    fprintf(stderr, "Prepare failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

// Query has 3 placeholders - must bind exactly 3 parameters
MYSQL_BIND bind[3];  // Must match placeholder count!
memset(bind, 0, sizeof(bind));

// Bind parameter 1: name (string)
const char *name = "John Doe";
unsigned long name_length = strlen(name);
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void *)name;
bind[0].buffer_length = name_length;
bind[0].length = &name_length;

// Bind parameter 2: email (string)
const char *email = "john@example.com";
unsigned long email_length = strlen(email);
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void *)email;
bind[1].buffer_length = email_length;
bind[1].length = &email_length;

// Bind parameter 3: age (integer)
int age = 30;
bind[2].buffer_type = MYSQL_TYPE_LONG;
bind[2].buffer = (void *)&age;

// NOW bind all parameters before execute
if (mysql_stmt_bind_param(stmt, bind)) {
    fprintf(stderr, "Bind failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

// ONLY NOW execute - all parameters are bound
if (mysql_stmt_execute(stmt)) {
    fprintf(stderr, "Execute failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

mysql_stmt_close(stmt);
  1. Count placeholders and verify matching parameters in C API
  2. Fix Node.js parameterized queries - verify parameter array length
  3. Fix PHP MySQLi prepared statements - bind all parameters before execute
  4. Fix conditional parameter binding logic
mysql โ€” error
#include <mysql.h>
#include <string.h>
MYSQL_STMT *stmt = mysql_stmt_init(mysql);
if (!stmt) {
fprintf(stderr, "mysql_stmt_init() failed\n");
return 1;
}
const char *query = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, query, strlen(query))) {
fprintf(stderr, "Prepare failed: %s\n", mysql_stmt_error(stmt));
mysql_stmt_close(stmt);
return 1;
}
// Query has 3 placeholders - must bind exactly 3 parameters
MYSQL_BIND bind[3]; // Must match placeholder count!
memset(bind, 0, sizeof(bind));
// Bind parameter 1: name (string)
const char *name = "John Doe";
unsigned long name_length = strlen(name);
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void *)name;
bind[0].buffer_length = name_length;
bind[0].length = &name_length;
// Bind parameter 2: email (string)
const char *email = "john@example.com";
unsigned long email_length = strlen(email);
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void *)email;
bind[1].buffer_length = email_length;
bind[1].length = &email_length;
// Bind parameter 3: age (integer)
int age = 30;
bind[2].buffer_type = MYSQL_TYPE_LONG;
bind[2].buffer = (void *)&age;
// NOW bind all parameters before execute
if (mysql_stmt_bind_param(stmt, bind)) {
fprintf(stderr, "Bind failed: %s\n", mysql_stmt_error(stmt));
mysql_stmt_close(stmt);
return 1;
}
// ONLY NOW execute - all parameters are bound
if (mysql_stmt_execute(stmt)) {
fprintf(stderr, "Execute failed: %s\n", mysql_stmt_error(stmt));
mysql_stmt_close(stmt);
return 1;
}
mysql_stmt_close(stmt);
CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters

What this error means

The CR_PARAMS_NOT_BOUND (2031) error is a MySQL C API client error that signals an attempt to execute a prepared statement where one or more placeholder parameters have not been bound with data. In MySQL's prepared statement protocol, parameters must be explicitly bound before execution: 1. **Prepare**: Create a prepared statement with `mysql_stmt_prepare()` containing `?` placeholders 2. **Bind Parameters**: Assign values to each placeholder using `mysql_stmt_bind_param()` 3. **Execute**: Run the statement with `mysql_stmt_execute()` - at this point, MySQL verifies all parameters are bound 4. **Fetch Results**: Retrieve results if applicable 5. **Close**: Clean up with `mysql_stmt_close()` Error 2031 occurs when step 3 (execute) is attempted but step 2 (bind) was skipped, incomplete, or improperly done. The most common causes are forgetting to bind parameters, binding the wrong number of parameters, or using the wrong binding function. This error is prevalent in Node.js, Python, PHP, and other language bindings where prepared statement lifecycle is exposed but not always obvious. It's also a sign of logic errors where conditional binding or loop-based binding may skip parameters unexpectedly.

Typical symptoms

  • Application throws "CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters" when executing a query
  • Parameterized queries work with some parameter values but fail with others
  • Code functions in development but fails in production when parameter arrays are dynamically constructed
  • Query execution fails sporadically based on conditional parameter binding logic
  • Error occurs only when using prepared statements, not with regular mysql_query()
  • Application crashes when executing multiple parameterized queries in sequence
  • CI/CD pipeline passes some tests but fails others that use different parameter counts

Common causes

  1. Called mysql_stmt_execute() without calling mysql_stmt_bind_param() first
  2. Number of bound parameters does not match the number of ? placeholders in the SQL query
  3. Parameter binding logic is conditional and skips some parameters under certain conditions
  4. Forgot to add parameters to the bind array when dynamically constructing queries
  5. Using wrong parameter binding method or passing NULL/undefined values to bind function
  6. Prepared statement handler is reused but bind parameters from previous execution weren't cleared
  7. Loop-generated INSERT statements with VALUES clauses have parameter count mismatches
  8. Binding parameters to the wrong statement handle or with the wrong data type

How to fix "CR_PARAMS_NOT_BOUND (2031): No data supplied for parameters"

1Count placeholders and verify matching parameters in C API

Ensure your SQL query has exactly as many ? placeholders as values you're binding:

c
#include <mysql.h>
#include <string.h>

MYSQL_STMT *stmt = mysql_stmt_init(mysql);
if (!stmt) {
    fprintf(stderr, "mysql_stmt_init() failed\n");
    return 1;
}

const char *query = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
if (mysql_stmt_prepare(stmt, query, strlen(query))) {
    fprintf(stderr, "Prepare failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

// Query has 3 placeholders - must bind exactly 3 parameters
MYSQL_BIND bind[3];  // Must match placeholder count!
memset(bind, 0, sizeof(bind));

// Bind parameter 1: name (string)
const char *name = "John Doe";
unsigned long name_length = strlen(name);
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void *)name;
bind[0].buffer_length = name_length;
bind[0].length = &name_length;

// Bind parameter 2: email (string)
const char *email = "john@example.com";
unsigned long email_length = strlen(email);
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = (void *)email;
bind[1].buffer_length = email_length;
bind[1].length = &email_length;

// Bind parameter 3: age (integer)
int age = 30;
bind[2].buffer_type = MYSQL_TYPE_LONG;
bind[2].buffer = (void *)&age;

// NOW bind all parameters before execute
if (mysql_stmt_bind_param(stmt, bind)) {
    fprintf(stderr, "Bind failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

// ONLY NOW execute - all parameters are bound
if (mysql_stmt_execute(stmt)) {
    fprintf(stderr, "Execute failed: %s\n", mysql_stmt_error(stmt));
    mysql_stmt_close(stmt);
    return 1;
}

mysql_stmt_close(stmt);

Count the ? marks in your SQL - your MYSQL_BIND array must have exactly that many elements.

2Fix Node.js parameterized queries - verify parameter array length

In Node.js with mysql or mysql2 drivers, ensure your parameter array matches placeholder count:

javascript
const mysql = require('mysql');
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'mydb'
});

// WRONG: 3 placeholders but only 2 parameters
const query1 = 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)';
const values1 = ['John', 'john@example.com'];  // Missing age!
// connection.query(query1, values1, callback);  // ERROR 2031!

// CORRECT: Match placeholder count
const values2 = ['John', 'john@example.com', 30];  // All 3 parameters
connection.query(query1, values2, (error, results) => {
  if (error) throw error;
  console.log('Insert successful');
});

// For SELECT with WHERE clause
const query3 = 'SELECT * FROM users WHERE id = ? AND active = ?';
const values3 = [123];  // WRONG: missing active parameter

const values4 = [123, 1];  // CORRECT: both parameters provided
connection.query(query3, values4, (error, results) => {
  if (error) throw error;
  console.log(results);
});

// Debugging: Log query and parameters
console.log('Query:', query3);
console.log('Parameters:', values4);
const placeholders = (query3.match(/\?/g) || []).length;
const provided = values4.length;
if (placeholders !== provided) {
  console.error(`Mismatch: query has ${placeholders} placeholders but ${provided} parameters provided`);
}

Always verify: placeholders_count === parameters_array.length

3Fix PHP MySQLi prepared statements - bind all parameters before execute

In PHP, ensure bind_param() is called before execute():

php
<?php
$mysqli = new mysqli("localhost", "user", "password", "database");

if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}

// Prepare statement with 3 placeholders
$query = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
$stmt = $mysqli->prepare($query);

if (!$stmt) {
    die("Prepare failed: " . $mysqli->error);
}

// WRONG: Only binding 2 parameters for a query with 3 placeholders
// $stmt->bind_param("ss", $name, $email);  // Missing age!
// $stmt->execute();  // ERROR 2031: missing age parameter

// CORRECT: Bind ALL parameters before execute
$name = "John Doe";
$email = "john@example.com";
$age = 30;

// bind_param signature: "types", var1, var2, var3, ...
// i = integer, d = double, s = string, b = blob
$stmt->bind_param("ssi", $name, $email, $age);

if (!$stmt->bind_param("ssi", $name, $email, $age)) {
    die("Bind failed: " . $stmt->error);
}

if (!$stmt->execute()) {
    die("Execute failed: " . $stmt->error);
}

echo "Insert successful";
$stmt->close();
$mysqli->close();
?>

The type string in bind_param() must have one character per placeholder: "ssi" for 3 parameters (string, string, integer).

4Fix conditional parameter binding logic

When parameters are conditionally added, ensure all ? placeholders are handled:

javascript
// WRONG: Building parameters conditionally can miss placeholders
let query = 'SELECT * FROM users WHERE 1=1';
let params = [];

if (filter.name) {
  query += ' AND name = ?';
  params.push(filter.name);
}

if (filter.email) {
  query += ' AND email = ?';
  params.push(filter.email);
}

if (filter.age) {
  query += ' AND age = ?';
  // Forgot to push filter.age!
}

// query might have 3 placeholders but only 2 parameters
db.query(query, params, callback);  // May error if age filter is set

// CORRECT: Always push parameter if adding placeholder
let query2 = 'SELECT * FROM users WHERE 1=1';
let params2 = [];

if (filter.name) {
  query2 += ' AND name = ?';
  params2.push(filter.name);
}

if (filter.email) {
  query2 += ' AND email = ?';
  params2.push(filter.email);
}

if (filter.age) {
  query2 += ' AND age = ?';
  params2.push(filter.age);  // ALWAYS push when adding ?
}

db.query(query2, params2, callback);

// Even better: Use a reusable helper
function buildQuery(filters) {
  const conditions = [];
  const params = [];
  
  for (const [key, value] of Object.entries(filters)) {
    if (value !== undefined && value !== null) {
      conditions.push(`${key} = ?`);
      params.push(value);
    }
  }
  
  return {
    query: `SELECT * FROM users WHERE ${conditions.join(' AND ')}`,
    params: params
  };
}

const { query: finalQuery, params: finalParams } = buildQuery(filter);
db.query(finalQuery, finalParams, callback);

The golden rule: Every ? in the query must have a corresponding value in the parameters array.

5Debug by printing query and parameter count before execution

Add logging to catch parameter mismatches early:

javascript
// JavaScript example
function executeQuery(sql, params) {
  // Validation
  const placeholderCount = (sql.match(/\?/g) || []).length;
  const paramCount = Array.isArray(params) ? params.length : 0;
  
  if (placeholderCount !== paramCount) {
    console.error('ERROR: Parameter count mismatch!');
    console.error(`SQL: ${sql}`);
    console.error(`Expected ${placeholderCount} parameters, got ${paramCount}`);
    console.error(`Parameters provided:`, params);
    throw new Error(`CR_PARAMS_NOT_BOUND: ${placeholderCount} placeholders but ${paramCount} parameters`);
  }
  
  console.log(`Executing: ${sql}`);
  console.log(`Parameters: ${JSON.stringify(params)}`);
  
  return db.query(sql, params);
}

// Usage
try {
  await executeQuery(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ['John']  // Missing email - will throw with helpful message
  );
} catch (err) {
  console.error(err.message);
}

This wrapper catches mismatches before they reach the database driver.

6Fix dynamic INSERT statements with loop-generated VALUES

Bulk inserts can easily have parameter mismatches:

javascript
// WRONG: Building placeholders without matching parameters
const users = [
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' }
];

let query = 'INSERT INTO users (name, email) VALUES ';
let values = [];

for (let i = 0; i < users.length; i++) {
  if (i > 0) query += ', ';
  query += '(?, ?)';
  // Forgot to push the actual values!
  // values.push(users[i].name, users[i].email);
}

db.query(query, values, callback);  // Query has 4 placeholders, values is empty!

// CORRECT: Synchronize placeholders with parameter push
let query2 = 'INSERT INTO users (name, email) VALUES ';
let values2 = [];

for (let i = 0; i < users.length; i++) {
  if (i > 0) query2 += ', ';
  query2 += '(?, ?)';
  values2.push(users[i].name, users[i].email);  // Push immediately
}

db.query(query2, values2, callback);

// Best practice: Use a helper function
function buildInsertQuery(table, records) {
  if (records.length === 0) throw new Error('No records to insert');
  
  const columns = Object.keys(records[0]);
  const placeholders = columns.map(() => '?').join(', ');
  const rowPlaceholders = Array(records.length)
    .fill(null)
    .map(() => `(${placeholders})`)
    .join(', ');
  
  const query = `INSERT INTO ${table} (${columns.join(', ')}) VALUES ${rowPlaceholders}`;
  const params = records.flatMap(record => columns.map(col => record[col]));
  
  return { query, params };
}

const { query: sql, params } = buildInsertQuery('users', users);
db.query(sql, params, callback);

When building multi-row inserts, push parameter values immediately after adding the placeholder.

7Handle prepared statement reuse correctly

If reusing statement objects, ensure parameters are rebound:

c
// WRONG: Reusing statement without rebinding for new values
MYSQL_STMT *stmt = mysql_stmt_init(mysql);
mysql_stmt_prepare(stmt, "INSERT INTO logs (message) VALUES (?)", 45);

MYSQL_BIND bind[1];
memset(bind, 0, sizeof(bind));

const char *msg1 = "First log";
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (void *)msg1;
bind[0].buffer_length = strlen(msg1);

mysql_stmt_bind_param(stmt, bind);
mysql_stmt_execute(stmt);

// Trying to execute again with different data
const char *msg2 = "Second log";
// WITHOUT rebinding, MySQL will still use msg1's data
mysql_stmt_execute(stmt);  // Uses msg1 again, not msg2!

// CORRECT: Rebind before each execute with different data
mysql_stmt_bind_param(stmt, bind);  // Rebind with msg1
mysql_stmt_execute(stmt);

bind[0].buffer = (void *)msg2;  // Change buffer pointer
bind[0].buffer_length = strlen(msg2);
mysql_stmt_bind_param(stmt, bind);  // Rebind with msg2
mysql_stmt_execute(stmt);  // Now executes with msg2

mysql_stmt_close(stmt);

Each time you change parameter values, you must call mysql_stmt_bind_param() again before execute().

Advanced notes

The CR_PARAMS_NOT_BOUND error reveals several architectural considerations:

1. Language Abstraction Leaks: Higher-level languages (JavaScript, Python, PHP) abstract the MySQL prepared statement protocol, but the error can still surface if abstraction layers incorrectly handle parameter binding. ORMs sometimes skip binding parameters if they detect optimal paths, leading to this error.

2. Dynamic Query Construction: Building queries dynamically (WHERE clauses, bulk inserts) is error-prone because placeholders and parameters are constructed separately. Modern SQL builders (knex.js, SQLAlchemy) generate both in sync to prevent this. When rolling your own, treat placeholder addition and parameter addition as a single atomic operation.

3. Parameter Type Mismatches: Some drivers (especially ODBC connectors) may also throw this error if parameter data types don't match the SQL query expectation, even if counts match. Always verify both count AND type.

4. Connection State: The MYSQL_BIND array is tied to the statement handle and connection state. Some operations (like switching databases with USE) invalidate prepared statement state. Reusing a statement across connection pool switches may cause this error.

5. Debugging with Query Logs: Enable MySQL's general query log to see exact SQL: SET GLOBAL general_log = 1; SET GLOBAL log_output = 'TABLE';. Then check mysql.general_log table. However, prepared statements are logged differently than bound values, so you may see ? instead of actual values.

6. Performance Implication of Fixes: Adding parameter validation at every query layer adds small overhead. Use validation helpers only in development/logging, not in critical hot paths. Use TypeScript or static analysis in production code to catch these at compile time.

7. Migration from mysql to mysql2: The mysql2 driver in Promise mode handles some parameter binding differently. When upgrading legacy mysql code to mysql2, watch for subtle differences in how parameters are validated.

8. Batch Operations: For batch operations (1000+ rows), consider using LOAD DATA INFILE instead of parameterized INSERT ... VALUES (...), (...) statements. It bypasses parameter binding entirely and is orders of magnitude faster for bulk data.

Frequently asked questions

What does "2031" mean?
The CR_PARAMS_NOT_BOUND (2031) error is a MySQL C API client error that signals an attempt to execute a prepared statement where one or more placeholder parameters have not been bound with data. In MySQL's prepared statement protocol, parameters must be explicitly bound before execution: 1. Prepare: Create a prepared statement with mysql_stmt_prepare() containing ?
How do I fix "2031"?
This MySQL client error occurs when a prepared statement is executed without binding values to all the parameter placeholders (?) defined in the SQL query. The error indicates a mismatch between the expected parameters and the data actually provided.
What causes "2031"?
Called mysql_stmt_execute() without calling mysql_stmt_bind_param() first Number of bound parameters does not match the number of ? placeholders in the SQL query Parameter binding logic is conditional and skips some parameters under certain conditions
Is the "2031" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading