apache/iceberg · error · IcebergAnalysisException
ALTER TABLE contains multiple ordering clauses
Error message
ALTER TABLE contains multiple ordering clauses
What it means
An ALTER TABLE write-spec statement may contain at most one ordering clause (e.g. ORDERED BY / LOCALLY ORDERED BY / UNORDERED). The AST builder throws IcebergAnalysisException when writeOrderingSpec appears more than once, because the resulting ordering would be ambiguous.
Solutions
- Combine columns in a single clause: ORDERED BY a, b DESC
- Remove the redundant/old ordering clause
- Use UNORDERED alone to clear ordering
Example fix
// before ALTER TABLE t WRITE ORDERED BY id ORDERED BY ts; // after ALTER TABLE t WRITE ORDERED BY id, ts;
Defensive patterns
Strategy: validation
Validate before calling
def countOrdering(stmt: String): Int = {
val s = stmt.toUpperCase(Locale.ROOT)
("ORDERED BY|r?".r.findAllIn(s).size) + s.sliding("UNORDERED".length).count(_ == "UNORDERED")
} Try / catch
try { spark.sql(stmt) } catch { case e: IcebergAnalysisException if e.getMessage.contains("multiple ordering clauses") => log.error("Combine ordering columns into one clause", e) } Prevention
- Express multi-column ordering as ORDERED BY a, b, not repeated clauses
- Replace (not append) ordering clauses in generated DDL
- Use UNORDERED alone to clear ordering
When it happens
Trigger: `ALTER TABLE t WRITE ORDERED BY id ORDERED BY ts` or `ORDERED BY a UNORDERED` — any statement with two writeOrderingSpec clauses.
Common situations: Script concatenation appending a new ordering without removing the old one, or trying to express multi-column ordering by repeating ORDERED BY instead of listing columns.
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/60e7a8f0cfee6098.
Report an issue: GitHub.
Appendix: source
Thrown at spark/v4.0/spark-extensions/src/main/scala/org/apache/spark/sql/catalyst/parser/extensions/IcebergSqlExtensionsAstBuilder.scala:261
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 IcebergAnalysisException("ALTER TABLE contains multiple distribution clauses")
}
if (writeSpec.writeOrderingSpec.size > 1) {
throw new IcebergAnalysisException("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)