{"record":{"id":"0fcf734d53f66656","repo":"apache/druid","slug":"column-s-from-orderby-must-also-appear-in-co","errorCode":null,"errorMessage":"Column [%s] from 'orderBy' must also appear in 'columns'.","messagePattern":"Column \\[(.+?)\\] from 'orderBy' must also appear in 'columns'\\.","errorType":"validation","errorClass":"IllegalArgumentException (IAE)","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/scan/ScanQuery.java","lineNumber":182,"sourceCode":"            \"Inconsistent number of columns[%d] and columnTypes[%d] specified!\",\n            columns.size(),\n            columnTypes.size()\n        );\n      }\n    }\n\n    final Pair<List<OrderBy>, Order> ordering = verifyAndReconcileOrdering(orderBysFromUser, orderFromUser);\n    this.orderBys = Preconditions.checkNotNull(ordering.lhs);\n    this.timeOrder = ordering.rhs;\n\n    if (this.columns != null && this.columns.size() > 0) {\n      // Validate orderBy. (Cannot validate when signature is empty, since that means \"discover at runtime\".)\n\n      for (final OrderBy orderByColumn : this.orderBys) {\n        if (!this.columns.contains(orderByColumn.getColumnName())) {\n          // Error message depends on how the user originally specified ordering.\n          if (orderBysFromUser != null) {\n            throw new IAE(\"Column [%s] from 'orderBy' must also appear in 'columns'.\", orderByColumn.getColumnName());\n          } else {\n            throw new IllegalArgumentException(\"The __time column must be selected if the results are time-ordered.\");\n          }\n        }\n      }\n    }\n\n    this.maxRowsQueuedForOrdering = validateAndGetMaxRowsQueuedForOrdering();\n    this.maxSegmentPartitionsOrderedInMemory = validateAndGetMaxSegmentPartitionsOrderedInMemory();\n  }\n\n  /**\n   * Verifies that the ordering of a query is solely determined by {@link #getTimeOrder()}. Required to actually\n   * execute queries, because {@link #getOrderBys()} is not yet understood by the query engines.\n   *\n   * @throws IllegalStateException if the ordering is not solely determined by {@link #getTimeOrder()}\n   */\n  public static void verifyOrderByForNativeExecution(final ScanQuery query)","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/scan/ScanQuery.java#L164-L200","documentation":"ScanQuery validates at construction time that every column named in 'orderBy' also appears in the query's 'columns' list. Druid throws this IAE because ordering requires reading the column during scan, so ordering on an unselected column is invalid. The specific message is used when the user explicitly supplied the orderBy list (as opposed to the implicit time-ordering case).","triggerScenarios":"Constructing a ScanQuery (or deserializing one from JSON) where query.getOrderBys() contains a column name absent from query.getColumns(); e.g. POSTing a scan query with columns:[\"a\"] and orderBy:[{\"columnName\":\"b\"}].","commonSituations":"Hand-written JSON scan queries where orderBy was edited without updating columns; SQL-generated scan queries after a projection change; clients that auto-add ordering on a timestamp column not included in the projection.","solutions":["Add the orderBy column name to the query's 'columns' array","Remove the orderBy entry for the column not selected","If ordering by __time implicitly, ensure __time is in columns or drop the ordering"],"exampleFix":"// before\n{\"queryType\":\"scan\",\"columns\":[\"country\"],\"orderBy\":[{\"columnName\":\"city\",\"direction\":\"ascending\"}]}\n// after\n{\"queryType\":\"scan\",\"columns\":[\"country\",\"city\"],\"orderBy\":[{\"columnName\":\"city\",\"direction\":\"ascending\"}]}","handlingStrategy":"validation","validationCode":"final Set<String> selected = new HashSet<>(query.getColumns());\nfor (ScanQuery.OrderBy ob : query.getOrderBys()) {\n  if (!selected.contains(ob.getColumnName())) {\n    throw new IllegalArgumentException(\"orderBy column not in columns: \" + ob.getColumnName());\n  }\n}","typeGuard":"boolean orderByCovered(ScanQuery q) {\n  Set<String> cols = new HashSet<>(q.getColumns());\n  return q.getOrderBys().stream().allMatch(o -> cols.contains(o.getColumnName()));\n}","tryCatchPattern":null,"preventionTips":["Keep orderBy columns as a subset of the columns list in query templates","Validate query JSON client-side before submission","When generating queries from UIs, auto-append orderBy columns to columns"],"tags":["druid","scan-query","validation","query-construction"],"backgroundTag":"invalid-query-parameter","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"}