apache/iceberg · error · IcebergAnalysisException

ALTER TABLE contains multiple ordering clauses

Error message

ALTER TABLE contains multiple ordering clauses

What it means

Analogous to the distribution check: a WRITE spec may carry at most one ordering clause, and the AST builder throws this IcebergAnalysisException when writeSpec.writeOrderingSpec contains more than one. Two ordering clauses would leave the resulting SortOrder ambiguous, so the statement is rejected at parse/AST-build time.

Solutions

  1. Keep a single ordering clause listing all sort keys: `ORDERED BY a, b` instead of two clauses.
  2. Choose either global `ORDERED BY` or `LOCALLY ORDERED BY` per statement, not both.
  3. Regenerate the DDL from one source of truth if tooling emitted duplicate clauses.

Example fix

-- before
ALTER TABLE t WRITE ORDERED BY a LOCALLY ORDERED BY b
-- after
ALTER TABLE t WRITE ORDERED BY a, b
Defensive patterns

Strategy: validation

Validate before calling

val orderCount = "(LOCALLY\\s+)?ORDERED\\s+BY".r.findAllMatchIn(writeSpec).size
require(orderCount <= 1, "Only one WRITE ordering clause is allowed")

Prevention

When it happens

Trigger: Executing `ALTER TABLE t WRITE ORDERED BY a ORDERED BY b`, or combining `ORDERED BY` with `LOCALLY ORDERED BY` in the same WRITE spec.

Common situations: Generated DDL appending default and user ordering; hand-editing an ALTER statement and leaving both the old and new ORDERED BY clauses in place.

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/fe7893222aa05ef5. Report an issue: GitHub.

Appendix: source

Thrown at spark/v4.2/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)