# 54011 — too_many_columns

> Source-backed reference for PostgreSQL SQLSTATE 54011.
---

# 54011

## At a glance {#at-a-glance}
`54011` reports that a physical tuple descriptor or a related object definition exceeds its fixed column limit. The source path determines whether the count is for table attributes, index keys plus `INCLUDE` columns, partition keys, statistics dimensions, or a row-producing definition.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `54011` |
| Condition | `too_many_columns` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_TOO_MANY_COLUMNS` |
| Aliases | `—` |

<!-- source facts: data/errcodes/54011.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}
Fixed messages include `number of columns (%d) exceeds limit (%d)`, `tables can have at most %d columns`, `cannot use more than %d columns in an index`, `cannot partition using more than %d columns`, and statistics-specific forms. Table counts operate on physical attribute numbers: a dropped column still occupies a tuple-descriptor slot, so the visible-column count can be lower than `relnatts`. Index key and `INCLUDE` entries, partition keys, and statistics dimensions use separate limits even though all use this SQLSTATE.

In the locked 18.6 build, the relevant constants are `MaxHeapAttributeNumber = 1600` user table attributes, `MaxTupleAttributeNumber = 1664` tuple attributes, `INDEX_MAX_KEYS = 32`, `PARTITION_MAX_KEYS = 32`, and `STATS_MAX_DIMENSIONS = 8`. These values belong to different guards; the actual count and limit fields in a server diagnostic remain authoritative.

## Diagnosis {#diagnosis}
Read the object type, actual count, maximum, and DDL operation from the message and context. For a table or inherited table, inspect `pg_class.relnatts` and `pg_attribute`, including `attisdropped`, rather than counting only visible names. For an index include `INCLUDE` columns in the DDL count; for partitioning and extended statistics use their own key/dimension counts. A function-in-FROM column definition list is another 54011 path with a row-shape limit.

## Response {#response}
Reduce or split the object definition, use a different index or partitioning design, or move derived data to a related table. If dropped physical slots are the cause, dropping another visible column will not reclaim them; an ordinary physical rewrite such as `VACUUM FULL` or `CLUSTER` also keeps the dropped attributes in the tuple descriptor. Plan a new table with the intended logical column layout, migrate data while preserving dependencies, and validate DDL against the named object limit. If an `ERROR` occurs inside an explicit transaction, use `ROLLBACK` or `ROLLBACK TO SAVEPOINT` before retrying; in autocommit, retry only after the schema change removes the actual limit.

## Versions {#versions}
The locked catalogue records this condition from 7.4; cited heap, index, table, partition, statistics, and row-shape paths are PostgreSQL 18.6 source. No oversized schema was created.

## Related {#related}
[`54000`](../54000/), [`54023`](../54023/), [`54001`](../54001/)

## Sources {#sources}
[`src/backend/access/common/heaptuple.c#L1131-L1134`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/access/common/heaptuple.c#L1131)

[`src/backend/access/common/indextuple.c#L87-L90`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/access/common/indextuple.c#L87)

[`src/backend/catalog/heap.c#L461-L464`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/catalog/heap.c#L461)

[`src/backend/commands/tablecmds.c#L2567-L2582`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/tablecmds.c#L2567)

[`src/backend/commands/indexcmds.c#L640-L664`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/indexcmds.c#L640)

[`src/backend/commands/statscmds.c#L215-L224`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/statscmds.c#L215)

[`src/backend/parser/parse_relation.c#L1935-L1946`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/parser/parse_relation.c#L1935)

[`src/include/access/htup_details.h#L34-L48`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/include/access/htup_details.h#L34)

[`src/include/pg_config_manual.h#L63-L74`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/include/pg_config_manual.h#L63)

[`src/include/statistics/statistics.h#L19`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/include/statistics/statistics.h#L19)

The structured [evidence record](../data/evidence/54011.json) records fixed messages and the source/runtime boundary.
