{"id":"867af560d166d577","repo":"jackc/pgx","slug":"too-many-rows-in-result-set","errorCode":null,"errorMessage":"too many rows in result set","messagePattern":"too many rows in result set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"conn.go","lineNumber":110,"sourceCode":"// Identifier a PostgreSQL identifier or name. Identifiers can be composed of\n// multiple parts such as [\"schema\", \"table\"] or [\"table\", \"column\"].\ntype Identifier []string\n\n// Sanitize returns a sanitized string safe for SQL interpolation.\nfunc (ident Identifier) Sanitize() string {\n\tparts := make([]string, len(ident))\n\tfor i := range ident {\n\t\ts := strings.ReplaceAll(ident[i], string([]byte{0}), \"\")\n\t\tparts[i] = `\"` + strings.ReplaceAll(s, `\"`, `\"\"`) + `\"`\n\t}\n\treturn strings.Join(parts, \".\")\n}\n\nvar (\n\t// ErrNoRows occurs when rows are expected but none are returned.\n\tErrNoRows = newProxyErr(sql.ErrNoRows, \"no rows in result set\")\n\t// ErrTooManyRows occurs when more rows than expected are returned.\n\tErrTooManyRows = errors.New(\"too many rows in result set\")\n)\n\nfunc newProxyErr(background error, msg string) error {\n\treturn &proxyError{\n\t\tmsg:        msg,\n\t\tbackground: background,\n\t}\n}\n\ntype proxyError struct {\n\tmsg        string\n\tbackground error\n}\n\nfunc (err *proxyError) Error() string { return err.msg }\n\nfunc (err *proxyError) Unwrap() error { return err.background }\n","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/jackc/pgx/blob/ec1a0befd22592cffffdeeb0a50311b506372f4c/conn.go#L92-L128","documentation":"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.","triggerScenarios":"Calling pgx.CollectExactlyOneRow(rows, fn) over a result set that yields 2+ rows (rows.Next() is true a second time at rows.go:516).","commonSituations":"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.","solutions":["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).","If more than one row is acceptable, use CollectOneRow (keeps the first, no error) or CollectRows.","If you want exactly-one semantics, keep CollectExactlyOneRow and treat ErrTooManyRows as a data-integrity signal worth alerting on.","Add/restore a UNIQUE constraint on the column(s) you assume identify a single row."],"exampleFix":"// before\nrow, err := pgx.CollectExactlyOneRow(rows, pgx.RowToAddrOfStructByPos[User])\n// err == pgx.ErrTooManyRows when duplicates exist\n\n// after — first match is fine\nrow, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByPos[User])","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isTooManyRows(err error) bool { return errors.Is(err, pgx.ErrTooManyRows) }","tryCatchPattern":"row, err := pgx.CollectExactlyOneRow(rows, fn)\nswitch {\ncase errors.Is(err, pgx.ErrTooManyRows):\n    // duplicate data; alert or tighten the query\ncase errors.Is(err, pgx.ErrNoRows):\n    // not found\ndefault:\n    return err\n}","preventionTips":["Use CollectOneRow when duplicates are acceptable.","Add LIMIT 1 or a uniqueness predicate to the query.","Enforce a UNIQUE constraint on the column(s) you assume single-row."],"tags":["rows","data-integrity","sentinel"],"analyzedSha":"ec1a0befd22592cffffdeeb0a50311b506372f4c","analyzedAt":"2026-08-04T22:52:11.263Z","schemaVersion":2}