apache/iceberg · error
ALTER TABLE contains multiple ordering clauses
Error message
ALTER TABLE contains multiple ordering clauses
What it means
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.
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
Example fix
// before ALTER TABLE t WRITE ORDERED BY a LOCALLY ORDERED BY b; // after ALTER TABLE t WRITE LOCALLY ORDERED BY a, b;
Defensive patterns
Strategy: validation
Validate before calling
val orderCount = "(ORDERED\\s+BY|LOCALLY\\s+ORDERED\\s+BY|UNORDERED)".r.findAllIn(sqlText).size require(orderCount <= 1, "Only one ordering clause allowed")
Try / catch
try { spark.sql(ddl) } catch { case e: AnalysisException if e.getMessage.contains("multiple ordering") => log.error("duplicate ordering clause"); throw e } Prevention
- 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
When it happens
Trigger: 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'.
Common situations: Attempting to specify global and local ordering simultaneously; generated SQL appending an ordering clause that already exists.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple distribution clauses
- ALTER TABLE contains multiple ordering clauses
- ALTER TABLE has no changes: missing both distribution and…
- ALTER TABLE has no changes: missing both distribution and…
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/48abbdf440382814.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v3.5/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala:274
val ordering = if (orderingSpec != null && orderingSpec.order != null) {
toSeq(orderingSpec.order.fields).map(typedVisit[(Term, SortDirection, NullOrder)])
} else {
Seq.empty
}
SetWriteDistributionAndOrdering(tableName, distributionMode, ordering)
}
private def toDistributionAndOrderingSpec(
writeSpec: WriteSpecContext): (WriteDistributionSpecContext, WriteOrderingSpecContext) = {
if (writeSpec.writeDistributionSpec.size > 1) {
throw new AnalysisException("ALTER TABLE contains multiple distribution clauses")
}
if (writeSpec.writeOrderingSpec.size > 1) {
throw new AnalysisException("ALTER TABLE contains multiple ordering clauses")
}
val distributionSpec = toBuffer(writeSpec.writeDistributionSpec).headOption.orNull
val orderingSpec = toBuffer(writeSpec.writeOrderingSpec).headOption.orNull
(distributionSpec, orderingSpec)
}
/**
* Create an order field.
*/
override def visitOrderField(ctx: OrderFieldContext): (Term, SortDirection, NullOrder) = {
val term = Spark3Util.toIcebergTerm(typedVisit[Transform](ctx.transform))
val direction = Option(ctx.ASC)
.map(_ => SortDirection.ASC)
.orElse(Option(ctx.DESC).map(_ => SortDirection.DESC))
.getOrElse(SortDirection.ASC)
val nullOrder = Option(ctx.FIRST)View on GitHub (pinned to 86d9c8fc54)