{"record":{"id":"46f53147f71c6ac8","repo":"hibernate/hibernate-orm","slug":"numeric-literal-position-used-in-group-by-d","errorCode":null,"errorMessage":"Numeric literal '${position}' used in 'group by' does not match a registered select item","messagePattern":"Numeric literal '(.+?)' used in 'group by' does not match a registered select item","errorType":"exception","errorClass":"SemanticException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java","lineNumber":1634,"sourceCode":"\t\tfinal var processingState = processingStateStack.getCurrent();\n\t\tfinal var processingQuery = processingState.getProcessingQuery();\n\t\tfinal var queryPart = sqmQueryPart( processingQuery );\n\t\tif ( child instanceof TerminalNode ) {\n\t\t\tif ( definedCollate ) {\n\t\t\t\t// This is syntactically disallowed\n\t\t\t\tthrow new SyntaxException( \"'collate' is not allowed for position based 'order by' or 'group by' items\" );\n\t\t\t}\n\t\t\telse if ( !allowPositionalOrAliases ) {\n\t\t\t\t// This is syntactically disallowed\n\t\t\t\tthrow new SyntaxException( \"Position based 'order by' is not allowed in 'over' or 'within group' clauses\" );\n\t\t\t}\n\n\t\t\tfinal int position = Integer.parseInt( child.getText() );\n\n\t\t\t// make sure this selection exists\n\t\t\tfinal var nodeByPosition = nodeByPosition( queryPart, position, processingState );\n\t\t\tif ( nodeByPosition == null ) {\n\t\t\t\tthrow new SemanticException( \"Numeric literal '\" + position\n\t\t\t\t\t\t+ \"' used in 'group by' does not match a registered select item\",\n\t\t\t\t\t\tquery );\n\t\t\t}\n\n\t\t\treturn new SqmAliasedNodeRef(\n\t\t\t\t\tposition,\n\t\t\t\t\tnodeBuilder.resolveExpressible( integerDomainType ),\n\t\t\t\t\tnodeBuilder\n\t\t\t);\n\t\t}\n\t\telse if ( child instanceof HqlParser.IdentifierContext identifierContext ) {\n\t\t\tfinal String identifierText = visitIdentifier( identifierContext );\n\t\t\tif ( queryPart instanceof SqmQueryGroup<?> ) {\n\t\t\t\t// If the current query part is a query group, check if the text matches\n\t\t\t\t// an attribute name of one of the selected SqmFrom elements or the path source name of a SqmPath\n\t\t\t\tSqmFrom<?, ?> found = null;\n\t\t\t\tint sqmPosition = 0;\n\t\t\t\tfinal var selections = queryPart.getFirstQuerySpec().getSelectClause().getSelections();","sourceCodeStart":1616,"sourceCodeEnd":1652,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java#L1616-L1652","documentation":"Thrown while Hibernate translates HQL: a positional reference in 'group by' (e.g. 'group by 2') was validated against the select list of the query part and no select item is registered at that 1-based position (nodeByPosition returned null). Hibernate must map the ordinal to an SqmAliasedNodeRef pointing at a real select item, so an out-of-range ordinal is a semantic error, not silently ignored.","triggerScenarios":"HQL such as 'select e.name from Employee e group by 2' when only one select item exists; 'group by 0'; an ordinal greater than the select-list size; dynamically assembled queries where the 'group by N' was computed against a different select list (e.g. branches of a set operation with different select counts).","commonSituations":"Porting native SQL where GROUP BY 1 is legal into HQL; off-by-one mistakes from assuming 0-based positions; builders that append group-by ordinals before the select list is finalized.","solutions":["Replace the ordinal with the actual expression or path: 'group by e.name'","If keeping ordinals, verify 1 <= n <= number of select items in that query part (positions are 1-based) and fix the index","Alias the select item and group by the alias instead of the position","For dynamic queries, build the select list first and derive ordinals from it, or always group by full expressions"],"exampleFix":"// before\nselect e.name, e.dept from Employee e group by 3\n\n// after\nselect e.name, e.dept from Employee e group by e.dept","handlingStrategy":"validation","validationCode":"static boolean validGroupByOrdinal(String hql, int n) {\n    String lower = hql.toLowerCase();\n    int s = lower.indexOf(\"select\") + 6;\n    int f = lower.indexOf(\" from \");\n    if (s < 6 || f < 0 || f <= s) return false;\n    String list = hql.substring(s, f);\n    if (list.isBlank()) return false;\n    int depth = 0, count = 1;\n    for (char c : list.toCharArray()) {\n        if (c == '(') depth++;\n        else if (c == ')') depth--;\n        else if (c == ',' && depth == 0) count++;\n    }\n    return n >= 1 && n <= count;\n}\n// before: em.createQuery(\"select e.name from Employee e group by \" + n)\nif (!validGroupByOrdinal(hql, n)) throw new IllegalArgumentException(\"group-by ordinal out of range: \" + n);","typeGuard":null,"tryCatchPattern":"try {\n    return session.createQuery(hql, Tuple.class).list();\n} catch (org.hibernate.query.sqm.SemanticException e) {\n    // UnknownEntityException also lands here (subclass)\n    throw new IllegalArgumentException(\"HQL failed semantic validation: \" + hql, e);\n}","preventionTips":["Prefer grouping by explicit expressions or aliases over ordinals in HQL","Treat ordinals as 1-based, and validate them against the select-list size when building queries dynamically","Run a cheap createQuery() smoke test at startup or test time for every stored HQL string"],"tags":["hql","group-by","positional-reference","hibernate","semantic-analysis"],"backgroundTag":"invalid-select-item-reference","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}