pentaho/pentaho-kettle · error · KettleDatabaseException

Couldn't prepare statement :

Error message

Couldn't prepare statement :

What it means

DimensionLookup builds an UPDATE statement for updating the current dimension row (e.g. setting date_to to infinity / updating non-key fields) and prepares it via the JDBC connection. A SQLException from prepareStatement is wrapped in this KettleDatabaseException including the failing SQL. It means the SQL text is not acceptable to the database at prepare time — usually a syntax or schema problem, not data.

Solutions

  1. Read the wrapped SQLException cause for the real database error and check the SQL printed in the message
  2. Verify the dimension table, schema and all configured key/lookup/update fields exist in the database
  3. Check the database connection type and identifier quoting (schema.table prefix) matches your actual database
  4. Grant the connection user UPDATE privileges on the dimension table
  5. Re-enter the field mappings in the step so stale columns are removed

Example fix

// before: step references removed column
// UPDATE dim_customer SET email = ? WHERE dim_customer_tk = ?  -- column 'email' dropped
// after: re-add the column or remove it from the step's update fields
ALTER TABLE dim_customer ADD COLUMN email VARCHAR(255);
Defensive patterns

Strategy: validation

Validate before calling

// Java: pre-flight the table and columns before preparing the statement
Database db = new Database(transMeta, databaseMeta);
db.connect();
if (!db.checkTableExists(schemaTable)) throw new IllegalStateException("Dimension table missing: " + schemaTable);
for (String f : updateFields) if (!db.getTableFields(schemaTable).indexOfValue(f) < 0) throw new IllegalStateException("Column missing: " + f);

Try / catch

try {
  dimUpdate(...);
} catch (KettleDatabaseException e) {
  if (e.getMessage().startsWith("Couldn't prepare statement")) {
    logError("Prepare failed, SQL=" + e.getMessage() + " cause=" + e.getCause());
    // fix SQL/privileges/schema, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: conn.prepareStatement(sql) throws SQLException while preparing the dimension-update statement built from the step's table, key field, date-from/to fields and update fields.

Common situations: Wrong table name/schema or missing privileges; quoting/identifier case issues between database types; a field listed in the step no longer exists in the table; incompatible characters in identifiers; connecting to a different DB type than the metadata assumes.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/19a25fe9c386655e. Report an issue: GitHub.

Appendix: source

Thrown at engine/src/main/java/org/pentaho/di/trans/steps/dimensionlookup/DimensionLookup.java:1333

          }
          comma = true;
          sql += meta.getDatabaseMeta().quoteField( valueMeta.getName() ) + " = ?" + Const.CR;
          data.dimensionUpdateRowMeta.addValueMeta( valueMeta );
        }
      }

      sql += "WHERE  " + meta.getDatabaseMeta().quoteField( meta.getKeyField() ) + " = ?";
      data.dimensionUpdateRowMeta
        .addValueMeta( new ValueMetaInteger( meta.getKeyField() ) ); // The tk

      try {
        if ( isDebug() ) {
          logDebug( "Preparing statement: [" + sql + "]" );
        }
        data.prepStatementDimensionUpdate =
          data.db.getConnection().prepareStatement( meta.getDatabaseMeta().stripCR( sql ) );
      } catch ( SQLException ex ) {
        throw new KettleDatabaseException( "Couldn't prepare statement :" + Const.CR + sql, ex );
      }
    }

    // Assemble information
    // New
    Object[] dimensionUpdateRow = new Object[ data.dimensionUpdateRowMeta.size() ];
    int updateIndex = 0;
    for ( int i = 0; i < data.fieldnrs.length; i++ ) {
      // Ignore last_version, last_updated, etc. These are handled below...
      //
      if ( data.fieldnrs[ i ] >= 0 ) {
        dimensionUpdateRow[ updateIndex++ ] = row[ data.fieldnrs[ i ] ];
      }
    }
    for ( int i = 0; i < meta.getFieldUpdate().length; i++ ) {
      switch ( meta.getFieldUpdate()[ i ] ) {
        case DimensionLookupMeta.TYPE_UPDATE_DATE_INSUP:
        case DimensionLookupMeta.TYPE_UPDATE_DATE_UPDATED:

View on GitHub (pinned to f3058517a1)