{"record":{"id":"fd62a324d2ffac25","repo":"apache/druid","slug":"cannot-order-by-a-non-numeric-aggregator-s","errorCode":null,"errorMessage":"Cannot order by a non-numeric aggregator[%s]","messagePattern":"Cannot order by a non-numeric aggregator\\[(.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GrouperBufferComparatorUtils.java","lineNumber":158,"sourceCode":"    for (OrderByColumnSpec orderSpec : limitSpec.getColumns()) {\n      needsReverse = orderSpec.getDirection() != OrderByColumnSpec.Direction.ASCENDING;\n      int dimIndex = OrderByColumnSpec.getDimIndexForOrderBy(orderSpec, dimensions);\n      if (dimIndex >= 0) {\n        comparators.add(dimComparators[dimIndex]);\n        orderByIndices.add(dimIndex);\n        needsReverses.add(needsReverse);\n      } else {\n        int aggIndex = OrderByColumnSpec.getAggIndexForOrderBy(orderSpec, Arrays.asList(aggregatorFactories));\n        if (aggIndex >= 0) {\n          final StringComparator stringComparator = orderSpec.getDimensionComparator();\n          final ColumnType valueType = aggregatorFactories[aggIndex].getIntermediateType();\n          // Aggregators start after dimensions\n          final int aggOffset = keySize + aggregatorOffsets[aggIndex];\n\n          aggCount++;\n\n          if (!valueType.isNumeric()) {\n            throw new IAE(\"Cannot order by a non-numeric aggregator[%s]\", orderSpec);\n          }\n\n          comparators.add(\n              makeNullHandlingBufferComparatorForNumericData(\n                  aggOffset,\n                  makeNumericBufferComparator(valueType, aggOffset, true, stringComparator)\n              )\n          );\n          needsReverses.add(needsReverse);\n        }\n      }\n    }\n\n    for (int i = 0; i < dimCount; i++) {\n      if (!orderByIndices.contains(i)) {\n        comparators.add(dimComparators[i]);\n        needsReverses.add(false); // default to Ascending order if dim is not in an orderby spec\n      }","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GrouperBufferComparatorUtils.java#L140-L176","documentation":"Thrown by bufferComparatorWithAggregators when an ORDER BY / limit column maps to an aggregator whose output value type is not numeric (e.g. STRING or COMPLEX). The group-by v2 engine's in-memory buffer comparator can only compare aggregator slots numerically, so a non-numeric aggregator in the ordering spec cannot be supported and the query is rejected at comparator construction time.","triggerScenarios":"Building a Grouper buffer comparator with an OrderByColumnSpec whose column resolves to a non-numeric aggregator output type (aggregatorOffset lookup succeeds but valueType.isNumeric() is false), e.g. ordering by a 'stringFirst'/'stringLast'/filtered-string or sketch aggregator output in a groupBy query with sort/limit columns.","commonSituations":"Users add topN/limit orderBy on a stringFirst, stringLast, earliest/latest-by (string output), or HLL sketch aggregator; queries migrated from other engines where ordering by strings is allowed; auto-generated dashboards sorting by whatever column the user clicked.","solutions":["Order by a numeric aggregator output instead, or add a numeric aggregator (e.g. longSum/count) computed from the same column and order by that.","If ordering by a string value is required, remove the sort column from the limit spec and sort client-side, or use a post-aggregator that yields a number.","Cast/convert the string aggregator to numeric where semantically valid (e.g. parse the string to a number in a downstream step or via a numeric aggregator at ingestion)."],"exampleFix":"// before: orderByColumnSpecs: [\"stringFirst(user)\"] with aggregator stringFirst(\"user\")\n// after\n{\"type\":\"longSum\",\"name\":\"userCount\",\"fieldName\":\"events\"}\n// order by \"userCount\" instead of the string aggregator output","handlingStrategy":"validation","validationCode":"for (String col : limitSpec.getColumns()) {\n  String type = resolveOutputType(query, col.getDimension());\n  if (type != null && !NUMERIC_TYPES.contains(type)) {\n    throw new IllegalArgumentException(\"Order-by column '\" + col.getDimension() + \"' has non-numeric aggregator type \" + type);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only order by numeric aggregator/post-aggregator outputs in groupBy queries","Check aggregator outputType before wiring it into the limit spec","Prefer explicit numeric aggregators (longSum/doubleSum) for sort keys"],"tags":["druid","group-by","aggregator","ordering"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}