{"record":{"id":"408ff9e2881a26ac","repo":"remix-run/remix","slug":"update-requires-at-least-one-change","errorCode":null,"errorMessage":"update() requires at least one change","messagePattern":"update\\(\\) requires at least one change","errorType":"exception","errorClass":"DataTableQueryError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database/query-execution.ts","lineNumber":387,"sourceCode":"\nasync function executeUpdate(\n  database: QueryExecutionContext,\n  table: AnyTable,\n  state: QueryState,\n  changes: Record<string, unknown>,\n  options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },\n): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {\n  let returning = options?.returning\n  assertReturningCapability(database.capabilities, 'update', returning)\n  let preparedChanges = prepareUpdateValues(\n    table,\n    changes as never,\n    database.now(),\n    options?.touch ?? true,\n  )\n\n  if (Object.keys(preparedChanges).length === 0) {\n    throw new DataTableQueryError('update() requires at least one change')\n  }\n\n  let result: DataManipulationResult\n\n  if (hasScopedWriteModifiers(state)) {\n    result = await database[runInTransaction](async (tx) => {\n      let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, state)\n      let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)\n\n      if (!primaryKeyPredicate) {\n        return {\n          affectedRows: 0,\n          insertId: undefined,\n          rows: returning ? [] : undefined,\n        }\n      }\n\n      return tx[executeOperation]({","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database/query-execution.ts#L369-L405","documentation":"update() requires the set of changes to contain at least one column after preparation. If the changes object is empty (or all its keys resolved to nothing after defaults/timestamps are excluded), an UPDATE with no SET clause would be invalid SQL, so a DataTableQueryError is thrown before hitting the database.","triggerScenarios":"await table.update({ where: ..., changes: {} }); passing a changes object whose only keys are filtered out during preparation (e.g. undefined-only or read-only columns).","commonSituations":"Building changes from a form or JSON PATCH body where no fields were submitted; spreading an object that turned out to be empty; forgetting to default a 'updatedAt' touch column when the user changed nothing.","solutions":["Skip the update when the changes object is empty: if (Object.keys(changes).length === 0) return","Provide at least one real column to change in the changes object","If you intended a touch-only update, include the timestamp column explicitly"],"exampleFix":"// before\nawait userTable.update({ where: { id }, changes: submittedChanges })\n\n// after\nif (Object.keys(submittedChanges).length > 0) {\n  await userTable.update({ where: { id }, changes: submittedChanges })\n}","handlingStrategy":"validation","validationCode":"if (Object.keys(changes).length === 0) {\n  return // nothing to update\n}\nawait table.update({ where, changes })","typeGuard":"function hasUpdateChanges(changes: Record<string, unknown>): boolean {\n  return Object.keys(changes).length > 0\n}","tryCatchPattern":"try {\n  await table.update({ where, changes })\n} catch (error) {\n  if (error instanceof DataTableQueryError && error.message === 'update() requires at least one change') {\n    return // treat as no-op\n  } else throw error\n}","preventionTips":["Guard PATCH-style handlers with an empty-changes check before calling update()","Strip-undefined helpers should count surviving keys, not assume fields exist","Consider an explicit updatedAt touch if an update must always occur"],"tags":["data-table","update","validation","empty-changes"],"backgroundTag":"empty-update-set","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}