{"record":{"id":"284697fc86415638","repo":"hibernate/hibernate-orm","slug":"assignment-referred-to-columns-from-multiple-table-284697","errorCode":null,"errorMessage":"Assignment referred to columns from multiple tables","messagePattern":"Assignment referred to columns from multiple tables","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java","lineNumber":137,"sourceCode":"\t\t\t\tupdatingTableGroup.getTableReferenceJoins().size() + 1\n\t\t);\n\n\t\tfor ( int i = 0; i < assignments.size(); i++ ) {\n\t\t\tfinal Assignment assignment = assignments.get( i );\n\t\t\tfinal List<ColumnReference> assignmentColumnRefs = assignment.getAssignable().getColumnReferences();\n\n\t\t\tTableReference assignmentTableReference = null;\n\n\t\t\tfor ( int c = 0; c < assignmentColumnRefs.size(); c++ ) {\n\t\t\t\tfinal ColumnReference columnReference = assignmentColumnRefs.get( c );\n\t\t\t\tfinal TableReference tableReference = resolveTableReference(\n\t\t\t\t\t\tcolumnReference,\n\t\t\t\t\t\ttableReferenceByAlias\n\t\t\t\t);\n\n\t\t\t\t// TODO: this could be fixed by introducing joins to DML statements\n\t\t\t\tif ( assignmentTableReference != null && !assignmentTableReference.equals( tableReference ) ) {\n\t\t\t\t\tthrow new IllegalStateException( \"Assignment referred to columns from multiple tables\" );\n\t\t\t\t}\n\n\t\t\t\tassignmentTableReference = tableReference;\n\t\t\t}\n\t\t\tassert assignmentTableReference != null;\n\n\t\t\tList<Assignment> assignmentsForTable = assignmentsByTable.get( assignmentTableReference );\n\t\t\tif ( assignmentsForTable == null ) {\n\t\t\t\tassignmentsForTable = new ArrayList<>();\n\t\t\t\tassignmentsByTable.put( assignmentTableReference, assignmentsForTable );\n\t\t\t}\n\t\t\tassignmentsForTable.add( assignment );\n\t\t}\n\n\t\t// For nullable tables we have to also generate an insert CTE\n\t\tfor (int i = 0; i < entityPersister.getTableSpan(); i++) {\n\t\t\tif ( entityPersister.isNullableTable( i ) ) {\n\t\t\t\tfinal String tableExpression = entityPersister.getTableName( i );","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java#L119-L155","documentation":"CteUpdateHandler buckets bulk-UPDATE assignments by table exactly like the insert handler: each single assignment's column references must resolve to ONE table of the mutated entity. When an assignment (typically an embedded/composite value or a split mapping) spans columns of two tables, this IllegalStateException aborts SQL generation. The source TODO notes the limitation could only be lifted by adding joins to DML statements.","triggerScenarios":"`update Person p set p.details = :d` where the Details embeddable maps columns into both the primary table and a @SecondaryTable; SET clauses whose assignable spans formula columns across tables; bulk updates on joined-inheritance entities with cross-table components, executed via CteMutationStrategy.","commonSituations":"@SecondaryTable modeling where an embeddable straddles tables; switching mutation strategy to CTE on PostgreSQL and suddenly hitting this on previously-working bulk updates; Hibernate 6.x regressions around secondary-table bulk updates.","solutions":["Set the scalar attributes individually so each assignment lands in one table: `set p.details.a = :a, p.details.b = :b` grouped so components stay single-table","Rework the mapping so each embedded/attribute maps to one table only","Remove the forced CTE mutation strategy to fall back to the table-based handler","Upgrade to the newest 6.x patch and check Hibernate JIRA; file a reproducer if the mapping is legitimate"],"exampleFix":"// before\nupdate Person p set p.details = :details\n\n// after (details split per table)\nupdate Person p set p.mainDetails = :main, p.extraDetails = :extra","handlingStrategy":"try-catch","validationCode":"// Before bulk-updating an embeddable, confirm all its columns share one table\n// (check @AttributeOverride/@Table mappings); otherwise set members individually\nif (assignmentIsEmbeddable(setPath)) {\n    assertEmbeddableSingleTable(entityMeta, setPath);\n}","typeGuard":null,"tryCatchPattern":"try {\n    em.createQuery(updateHql).executeUpdate();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"multiple tables\")) {\n        throw new MappingIssueException(\n            \"One SET assignment spans two tables — split it per table or per attribute\", e);\n    }\n    throw e;\n}","preventionTips":["Assign scalar attributes instead of whole embeddables in bulk updates","Map embeddables so all their columns live in a single table when using bulk DML","Exercise bulk updates on secondary-table entities in integration tests before upgrading Hibernate"],"tags":["hql","bulk-update","cte","secondary-table","embeddable","hibernate-6"],"backgroundTag":"multi-table-dml-mutation","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}