jackc/pgx · error

no more results in batch

Error message

no more results in batch

What it means

Returned by (*batchResults).Exec when the underlying MultiResultReader reports no further result sets. pgx asked PostgreSQL for the next result of a pipelined batch and got ReadyForQuery instead, so the read index advanced past the last executed query. The message means the client consumed more result slots than it queued (or a server-side error aborted the remaining statements, suppressing their result sets).

Source

Thrown at batch.go:143

	closed    bool
	endTraced bool
}

// Exec reads the results from the next query in the batch as if the query has been sent with Exec.
func (br *batchResults) Exec() (pgconn.CommandTag, error) {
	if br.err != nil {
		return pgconn.CommandTag{}, br.err
	}
	if br.closed {
		return pgconn.CommandTag{}, fmt.Errorf("batch already closed")
	}

	query, arguments, _ := br.nextQueryAndArgs()

	if !br.mrr.NextResult() {
		err := br.mrr.Close()
		if err == nil {
			err = errors.New("no more results in batch")
		}
		if br.conn.batchTracer != nil {
			br.conn.batchTracer.TraceBatchQuery(br.ctx, br.conn, TraceBatchQueryData{
				SQL:  query,
				Args: arguments,
				Err:  err,
			})
		}
		return pgconn.CommandTag{}, err
	}

	commandTag, err := br.mrr.ResultReader().Close()
	if err != nil {
		br.err = err
		br.mrr.Close()
	}

	if br.conn.batchTracer != nil {

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Drive results through callbacks via QueuedQuery.Exec/Query/QueryRow and rely on BatchResults.Close, instead of manually calling Exec() N times.
  2. Make sure the number of result reads (Exec/Query/QueryRow) equals Batch.Len(); track the count in the same place you call Queue.
  3. Inspect any error returned before this one (br.err / the previous Exec return) — a server error earlier in the batch will make every subsequent read surface 'no more results in batch' as a secondary symptom.
  4. If you conditionally skip Queue calls, conditionally skip the matching reads in the same branch.

Example fix

// before
br := conn.SendBatch(ctx, batch)
for i := 0; i < 5; i++ { // hardcoded count drifted from batch.Len()
    _, err := br.Exec()
    if err != nil { return err } // hits "no more results in batch"
}
br.Close()

// after
br := conn.SendBatch(ctx, batch)
for i := 0; i < batch.Len(); i++ {
    _, err := br.Exec()
    if err != nil { return err }
}
return br.Close()
Defensive patterns

Strategy: validation

Validate before calling

if batch.Len() == 0 { return nil }
readCount := 0
br := conn.SendBatch(ctx, batch)
for readCount < batch.Len() {
    if _, err := br.Exec(); err != nil { _ = br.Close(); return err }
    readCount++
}
return br.Close()

Try / catch

br := conn.SendBatch(ctx, batch)
defer func() { _ = br.Close() }()
for i := 0; i < batch.Len(); i++ {
    if _, err := br.Exec(); err != nil {
        if err.Error() == "no more results in batch" {
            // earlier query failed and aborted the batch; check prior error
        }
        return err
    }
}

Prevention

When it happens

Trigger: Calling Exec() on the BatchResults returned by Conn.SendBatch more times than Batch.Queue was called. Also reachable when an earlier query in the batch errors on the server: PostgreSQL aborts the implicit transaction and emits no result sets for the remaining queued queries, so a subsequent Exec() finds nothing.

Common situations: Mismatch between the number of Queue() calls and the number of Exec()/Query()/Close()-driven reads; a failed earlier statement that short-circuits the batch; upgrading a loop that previously added an extra read for a trailing row count.

Related errors


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/ba3d8856d72b6230.json. Report an issue: GitHub.