{"record":{"id":"48abbdf440382814","repo":"apache/iceberg","slug":"alter-table-contains-multiple-ordering-clauses","errorCode":null,"errorMessage":"ALTER TABLE contains multiple ordering clauses","messagePattern":"ALTER TABLE contains multiple ordering clauses","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala","lineNumber":274,"sourceCode":"\n    val ordering = if (orderingSpec != null && orderingSpec.order != null) {\n      toSeq(orderingSpec.order.fields).map(typedVisit[(Term, SortDirection, NullOrder)])\n    } else {\n      Seq.empty\n    }\n\n    SetWriteDistributionAndOrdering(tableName, distributionMode, ordering)\n  }\n\n  private def toDistributionAndOrderingSpec(\n      writeSpec: WriteSpecContext): (WriteDistributionSpecContext, WriteOrderingSpecContext) = {\n\n    if (writeSpec.writeDistributionSpec.size > 1) {\n      throw new AnalysisException(\"ALTER TABLE contains multiple distribution clauses\")\n    }\n\n    if (writeSpec.writeOrderingSpec.size > 1) {\n      throw new AnalysisException(\"ALTER TABLE contains multiple ordering clauses\")\n    }\n\n    val distributionSpec = toBuffer(writeSpec.writeDistributionSpec).headOption.orNull\n    val orderingSpec = toBuffer(writeSpec.writeOrderingSpec).headOption.orNull\n\n    (distributionSpec, orderingSpec)\n  }\n\n  /**\n   * Create an order field.\n   */\n  override def visitOrderField(ctx: OrderFieldContext): (Term, SortDirection, NullOrder) = {\n    val term = Spark3Util.toIcebergTerm(typedVisit[Transform](ctx.transform))\n    val direction = Option(ctx.ASC)\n      .map(_ => SortDirection.ASC)\n      .orElse(Option(ctx.DESC).map(_ => SortDirection.DESC))\n      .getOrElse(SortDirection.ASC)\n    val nullOrder = Option(ctx.FIRST)","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala#L256-L292","documentation":"Iceberg allows at most one ordering clause per ALTER TABLE ... WRITE statement. A second writeOrderingSpec element causes this AnalysisException because ordering cannot be both e.g. UNORDERED and ORDERED BY multiple specs.","triggerScenarios":"Writing two ordering clauses in one statement, e.g. 'ALTER TABLE t WRITE ORDERED BY a LOCALLY ORDERED BY b' or '... ORDERED BY a UNORDERED'.","commonSituations":"Attempting to specify global and local ordering simultaneously; generated SQL appending an ordering clause that already exists.","solutions":["Keep a single ordering clause: choose ORDERED BY, LOCALLY ORDERED BY, or UNORDERED","Merge multiple ordering columns into one ORDERED BY col1, col2, ... list","Run separate ALTER TABLE statements if replacing an existing write order"],"exampleFix":"// before\nALTER TABLE t WRITE ORDERED BY a LOCALLY ORDERED BY b;\n// after\nALTER TABLE t WRITE LOCALLY ORDERED BY a, b;","handlingStrategy":"validation","validationCode":"val orderCount = \"(ORDERED\\\\s+BY|LOCALLY\\\\s+ORDERED\\\\s+BY|UNORDERED)\".r.findAllIn(sqlText).size\nrequire(orderCount <= 1, \"Only one ordering clause allowed\")","typeGuard":null,"tryCatchPattern":"try { spark.sql(ddl) } catch { case e: AnalysisException if e.getMessage.contains(\"multiple ordering\") => log.error(\"duplicate ordering clause\"); throw e }","preventionTips":["Combine all ordering columns into one ORDERED BY a, b list","Never pair UNORDERED with an ORDERED BY clause in the same statement","Validate generated DDL for repeated ordering keywords"],"tags":["spark","sql","alter-table"],"backgroundTag":"conflicting-config-options","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}