{"record":{"id":"b3d5130440771a7e","repo":"hibernate/hibernate-orm","slug":"not-expecting-multiple-table-references-for-an-sqm","errorCode":null,"errorMessage":"Not expecting multiple table references for an SQM INSERT-SELECT","messagePattern":"Not expecting multiple table references for an SQM INSERT-SELECT","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java","lineNumber":1236,"sourceCode":"\t\t\t);\n\n\t\t\tgetFromClauseAccess().registerTableGroup( rootPath, rootTableGroup );\n\n\t\t\tinsertStatement = new InsertSelectStatement(\n\t\t\t\t\tcteContainer,\n\t\t\t\t\t(NamedTableReference) rootTableGroup.getPrimaryTableReference(),\n\t\t\t\t\tentityDescriptor,\n\t\t\t\t\temptyList()\n\t\t\t);\n\t\t\tadditionalInsertValues = visitInsertionTargetPaths(\n\t\t\t\t\t(assigable, references) -> insertStatement.addTargetColumnReferences( references ),\n\t\t\t\t\tsqmStatement,\n\t\t\t\t\tentityDescriptor,\n\t\t\t\t\trootTableGroup\n\t\t\t);\n\n\t\t\tif ( hasJoins( rootTableGroup ) ) {\n\t\t\t\tthrow new SemanticException( \"Not expecting multiple table references for an SQM INSERT-SELECT\" );\n\t\t\t}\n\t\t}\n\t\tfinally {\n\t\t\tpopProcessingStateStack();\n\t\t\tcurrentClauseStack.pop();\n\t\t}\n\n\t\tinsertStatement.setSourceSelectStatement(\n\t\t\t\tvisitQueryPart( selectQueryPart )\n\t\t);\n\n\t\tinsertStatement.getSourceSelectStatement().visitQuerySpecs(\n\t\t\t\tquerySpec -> {\n\t\t\t\t\tfinal boolean appliedRowNumber =\n\t\t\t\t\t\t\tadditionalInsertValues.applySelections( querySpec, getSessionFactory() );\n\t\t\t\t\t// Just make sure that if we get here, a row number will never be applied\n\t\t\t\t\t// If this requires the special row number handling, it should use the mutation strategy\n\t\t\t\t\tassert !appliedRowNumber;","sourceCodeStart":1218,"sourceCodeEnd":1254,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/spi/BaseSqmToSqlAstConverter.java#L1218-L1254","documentation":"Translating an HQL INSERT ... SELECT, Hibernate builds a root table group for the insertion target and requires it to reference exactly one table. If that table group ends up with joins (secondary tables via @SecondaryTable, JOINED inheritance pieces, secondary table group joins triggered by the target's mapped attributes), the insert cannot be rendered as a single INSERT ... SELECT and visitInsertSelectStatement throws SemanticException('Not expecting multiple table references for an SQM INSERT-SELECT').","triggerScenarios":"HQL 'insert into Person (id, name) select ... from Person p' where Person is mapped with @SecondaryTable or JOINED inheritance so createRootTableGroup produces joined table references; inserting into an entity whose target paths (visitInsertionTargetPaths) force joins to other tables; entities with @OneToMany join-table style secondary mappings referenced in the target column list.","commonSituations":"Bulk-copy/ETL jobs using HQL insert-select on entities with secondary tables; testing insert-select against a simpler entity worked, then pointing it at a richer mapped entity; inheritance refactoring (SINGLE_TABLE to JOINED) breaking existing insert-select statements; Hibernate 5 -> 6/7 migration where such statements previously rendered with different table-group logic.","solutions":["Restructure the mapping: remove @SecondaryTable for the target entity or switch JOINED inheritance to SINGLE_TABLE (with discriminator) so one table suffices","Write the insert with native SQL (session.createNativeMutationQuery) covering both tables explicitly","Insert into a single-table staging/DTO entity and post-process, or perform per-row persists for the multi-table case","Verify which mapping element creates the extra table reference (enable hibernate.show_sql / trace org.hibernate.SQL) and move the offending columns into the primary table"],"exampleFix":"-- before: Person has @SecondaryTable(\"person_details\")\ninsert into Person (id, name, bio) select p.id, p.name, p.bio from Person p where ...\n-- after: native SQL covering both tables\ninsert into person (id, name) select p.id, p.name from person p;\ninsert into person_details (id, bio) select p.id, p.bio from person p;","handlingStrategy":"fallback","validationCode":"// Only route insert-select to HQL when target is single-table\nEntityPersister persister = session.getEntityPersister(Person.class.getName(), null);\nboolean singleTable = persister.getPropertySpans() <= 1 || !persister.hasSubclasses();\n// heuristic: check factory metadata for secondary tables before choosing HQL vs native SQL","typeGuard":"static boolean singleTableTarget(SessionFactory sf, String entityName) {\n    var descr = sf.getMetamodel().getEntityDescriptor(entityName);\n    return descr.getTableReferences() == null || descr.getPropertySpan() == descr.getTableReferences().size();\n}","tryCatchPattern":"catch (SemanticException e) { if (e.getMessage().contains(\"multiple table references\")) { /* fall back to native multi-table INSERT or per-row persist */ } else throw e; }","preventionTips":["Reserve HQL insert-select for single-table entities","After adding @SecondaryTable or switching inheritance strategy, retest all bulk inserts","Maintain a mapping inventory of which entities are bulk-insert-safe"],"tags":["hibernate","hql","insert-select","bulk-insert","secondary-table","joined-inheritance","table-group"],"backgroundTag":"hql-insert-select-secondary-table","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}