jackc/pgx · error

too many rows in result set

Error message

too many rows in result set

What it means

ErrTooManyRows is a package-level sentinel (errors.New at conn.go:110) raised by CollectExactlyOneRow when a second row is available after scanning the first. It signals that a query expected to match exactly one row matched more than one — a data-uniqueness violation rather than a protocol error. QueryRow itself silently keeps the first row, so this sentinel is specific to the CollectExactlyOneRow helper.

Solutions

  1. Tighten the WHERE clause so it selects at most one row (add the primary key or a LIMIT 1 if 'first match' semantics are intended).
  2. If more than one row is acceptable, use CollectOneRow (keeps the first, no error) or CollectRows.
  3. If you want exactly-one semantics, keep CollectExactlyOneRow and treat ErrTooManyRows as a data-integrity signal worth alerting on.
  4. Add/restore a UNIQUE constraint on the column(s) you assume identify a single row.

Example fix

// before
row, err := pgx.CollectExactlyOneRow(rows, pgx.RowToAddrOfStructByPos[User])
// err == pgx.ErrTooManyRows when duplicates exist

// after — first match is fine
row, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByPos[User])
Defensive patterns

Strategy: try-catch

Type guard

func isTooManyRows(err error) bool { return errors.Is(err, pgx.ErrTooManyRows) }

Try / catch

row, err := pgx.CollectExactlyOneRow(rows, fn)
switch {
case errors.Is(err, pgx.ErrTooManyRows):
    // duplicate data; alert or tighten the query
case errors.Is(err, pgx.ErrNoRows):
    // not found
default:
    return err
}

Prevention

When it happens

Trigger: Calling pgx.CollectExactlyOneRow(rows, fn) over a result set that yields 2+ rows (rows.Next() is true a second time at rows.go:516).

Common situations: A WHERE clause missing a uniqueness predicate; a join that fans out rows; a table whose unique constraint was dropped; querying a lookup table that gained a duplicate.


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

Appendix: source

Thrown at conn.go:110

// Identifier a PostgreSQL identifier or name. Identifiers can be composed of
// multiple parts such as ["schema", "table"] or ["table", "column"].
type Identifier []string

// Sanitize returns a sanitized string safe for SQL interpolation.
func (ident Identifier) Sanitize() string {
	parts := make([]string, len(ident))
	for i := range ident {
		s := strings.ReplaceAll(ident[i], string([]byte{0}), "")
		parts[i] = `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
	}
	return strings.Join(parts, ".")
}

var (
	// ErrNoRows occurs when rows are expected but none are returned.
	ErrNoRows = newProxyErr(sql.ErrNoRows, "no rows in result set")
	// ErrTooManyRows occurs when more rows than expected are returned.
	ErrTooManyRows = errors.New("too many rows in result set")
)

func newProxyErr(background error, msg string) error {
	return &proxyError{
		msg:        msg,
		background: background,
	}
}

type proxyError struct {
	msg        string
	background error
}

func (err *proxyError) Error() string { return err.msg }

func (err *proxyError) Unwrap() error { return err.background }

View on GitHub (pinned to ec1a0befd2)