Index names in PostgreSQL

  • Index names are unique across a single database schema.
  • Index names cannot be the same as any other index, (foreign) table, (materialized) view, sequence or user-defined composite type in the same schema.
  • Two tables in the same schema cannot have an index of the same name. (Follows logically.)

If you do not care about the name of the index, have Postgres auto-name it:

CREATE INDEX ON tbl1 (col1);

is (almost) the same as:

CREATE INDEX tbl1_col1_idx ON tbl1 USING btree (col1);

Except that Postgres will avoid a naming collisions and automatically pick the next free name:

tbl1_col1_idx 
tbl1_col1_idx2
tbl1_col1_idx3
...

Just try it. But, obviously, you would not want to create multiple redundant indexes. So it wouldn't be a good idea to just blindly create a new one.

Test for existence

Postgres 9.5 or newer

Now available:

CREATE INDEX IF NOT EXISTS ...

Also works for CREATE INDEX CONCURRENTLY IF NOT EXISTS.

However, the manual warns:

Note that there is no guarantee that the existing index is anything like the one that would have been created.

It's a plain check for the object name. (Applies to variants for older versions below, too.)
To find existing indexes on the same table for the same column(s):

SELECT pg_get_indexdef(indexrelid)
FROM   pg_index
WHERE  indrelid = 'public.big'::regclass
AND   (indkey::int2[])[:] = ARRAY (
   SELECT attnum
   FROM   unnest('{usr_id, created_at}'::text[]) WITH ORDINALITY i(attname, ord)
   JOIN  (
      SELECT attname, attnum
      FROM   pg_attribute
      WHERE  attrelid = 'public.big'::regclass
      ) a USING (attname)
   ORDER BY ord
   );

Restrictions:

  • Only works for columns, not other index expressions.
  • Also reports partial indexes (with WHERE clause) and covering indexes (with INCLUDE clause).
  • Reports any type of index, not just B-tree indexes.

Study the results (if any) before proceeding, or refine the query to your needs ...

Further reading:

  • Find tables with multiple indexes on same column
  • Normalize array subscripts for 1-dimensional array so they start with 1

Postgres 9.4

You can use the new function to_regclass() to check without throwing an exception:

DO
$$
BEGIN
   IF to_regclass('myschema.mytable_mycolumn_idx') IS NULL THEN
      CREATE INDEX mytable_mycolumn_idx ON myschema.mytable (mycolumn);
   END IF;

END
$$;

Returns NULL if an index (or another object) of that name does not exist. See:

  • How to check if a table exists in a given schema

This doesn't work for CREATE INDEX CONCURRENTLY, since that variant cannot be wrapped in an outer transaction. See comment by @Gregory below.

Postgres 9.3 or older

Cast the schema-qualified name to regclass:

SELECT 'myschema.myname'::regclass;

If it throws an exception, the name is free.
Or, to test the same without throwing an exception, use a DO statement:

DO
$$
BEGIN
   IF NOT EXISTS (
      SELECT
      FROM   pg_class c
      JOIN   pg_namespace n ON n.oid = c.relnamespace
      WHERE  c.relname = 'mytable_mycolumn_idx'
      AND    n.nspname = 'myschema'
   ) THEN
    
        CREATE INDEX mytable_mycolumn_idx ON myschema.mytable (mycolumn);
    END IF;
END
$$;

The DO statement was introduced with Postgres 9.0. In earlier versions you have to create a function to do the same.
Details about pg_class in the manual.
Basics about indexes in the manual.

Answer from Erwin Brandstetter on Stack Exchange
🌐
PostgreSQL
postgresql.org › docs › current › sql-createindex.html
PostgreSQL: Documentation: 18: CREATE INDEX
May 14, 2026 - Even then, however, the index may not be immediately usable for queries: in the worst case, it cannot be used as long as transactions exist that predate the start of the index build. If a problem arises while scanning the table, such as a deadlock or a uniqueness violation in a unique index, the CREATE INDEX command will fail but leave behind an “invalid” index. This index will be ignored for querying purposes because it might be incomplete; however it will still consume update overhead. The psql \d command will report such an index as INVALID: postgres=# \d tab Table "public.tab" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- col | integer | | | Indexes: "idx" btree (col) INVALID
Top answer
1 of 2
137

Index names in PostgreSQL

  • Index names are unique across a single database schema.
  • Index names cannot be the same as any other index, (foreign) table, (materialized) view, sequence or user-defined composite type in the same schema.
  • Two tables in the same schema cannot have an index of the same name. (Follows logically.)

If you do not care about the name of the index, have Postgres auto-name it:

CREATE INDEX ON tbl1 (col1);

is (almost) the same as:

CREATE INDEX tbl1_col1_idx ON tbl1 USING btree (col1);

Except that Postgres will avoid a naming collisions and automatically pick the next free name:

tbl1_col1_idx 
tbl1_col1_idx2
tbl1_col1_idx3
...

Just try it. But, obviously, you would not want to create multiple redundant indexes. So it wouldn't be a good idea to just blindly create a new one.

Test for existence

Postgres 9.5 or newer

Now available:

CREATE INDEX IF NOT EXISTS ...

Also works for CREATE INDEX CONCURRENTLY IF NOT EXISTS.

However, the manual warns:

Note that there is no guarantee that the existing index is anything like the one that would have been created.

It's a plain check for the object name. (Applies to variants for older versions below, too.)
To find existing indexes on the same table for the same column(s):

SELECT pg_get_indexdef(indexrelid)
FROM   pg_index
WHERE  indrelid = 'public.big'::regclass
AND   (indkey::int2[])[:] = ARRAY (
   SELECT attnum
   FROM   unnest('{usr_id, created_at}'::text[]) WITH ORDINALITY i(attname, ord)
   JOIN  (
      SELECT attname, attnum
      FROM   pg_attribute
      WHERE  attrelid = 'public.big'::regclass
      ) a USING (attname)
   ORDER BY ord
   );

Restrictions:

  • Only works for columns, not other index expressions.
  • Also reports partial indexes (with WHERE clause) and covering indexes (with INCLUDE clause).
  • Reports any type of index, not just B-tree indexes.

Study the results (if any) before proceeding, or refine the query to your needs ...

Further reading:

  • Find tables with multiple indexes on same column
  • Normalize array subscripts for 1-dimensional array so they start with 1

Postgres 9.4

You can use the new function to_regclass() to check without throwing an exception:

DO
$$
BEGIN
   IF to_regclass('myschema.mytable_mycolumn_idx') IS NULL THEN
      CREATE INDEX mytable_mycolumn_idx ON myschema.mytable (mycolumn);
   END IF;

END
$$;

Returns NULL if an index (or another object) of that name does not exist. See:

  • How to check if a table exists in a given schema

This doesn't work for CREATE INDEX CONCURRENTLY, since that variant cannot be wrapped in an outer transaction. See comment by @Gregory below.

Postgres 9.3 or older

Cast the schema-qualified name to regclass:

SELECT 'myschema.myname'::regclass;

If it throws an exception, the name is free.
Or, to test the same without throwing an exception, use a DO statement:

DO
$$
BEGIN
   IF NOT EXISTS (
      SELECT
      FROM   pg_class c
      JOIN   pg_namespace n ON n.oid = c.relnamespace
      WHERE  c.relname = 'mytable_mycolumn_idx'
      AND    n.nspname = 'myschema'
   ) THEN
    
        CREATE INDEX mytable_mycolumn_idx ON myschema.mytable (mycolumn);
    END IF;
END
$$;

The DO statement was introduced with Postgres 9.0. In earlier versions you have to create a function to do the same.
Details about pg_class in the manual.
Basics about indexes in the manual.

2 of 2
13

It will be available in 9.5. Here is actual git commit https://github.com/postgres/postgres/commit/08309aaf74ee879699165ec8a2d53e56f2d2e947

Discussion on pg hackers http://postgresql.nabble.com/CREATE-IF-NOT-EXISTS-INDEX-td5821173.html

Discussions

postgresql - why would CREATE INDEX IF NOT EXISTS timeout, with postgres? - Stack Overflow
I have a rather large table (tens of millions of rows) and the app's startup script is running a few things, including the line: CREATE INDEX IF NOT EXISTS idx_trades_ticker_ts ON exchange.trades (... More on stackoverflow.com
🌐 stackoverflow.com
Don't rely on IF NOT EXISTS for concurrent index creation in PostgreSQL
In my experience on large tables that are “busy”, sometimes indexes need to be added manually first from a utility session, perhaps inside tmux/screen that's detached from while they are created. This could take hours for large tables. Then once done, and the index is valid, an Active Record ... More on news.ycombinator.com
🌐 news.ycombinator.com
54
90
August 14, 2024
Missing support for CREATE INDEX CONCURRENTLY IF NOT EXISTS - ORM - Django Forum
Django doesn’t support the IF NOT EXISTS conditional when creating indexes concurrently in postgres. Has anyone else come across this? There is a ticket (just created) for that. Good to check if more people are interested in this: https://code.djangoproject.com/ticket/35383 More on forum.djangoproject.com
🌐 forum.djangoproject.com
0
April 17, 2024
CREATE INDEX IF NOT EXISTS throws an error
when using CREATE INDEX IF NOT EXISTS, I get an error syntax error at or near "NOT" If I remove IF NOT EXISTS, it's working properly This modifier was added in postgresql 9.5 I'm ... More on github.com
🌐 github.com
1
March 25, 2019
🌐
Shayon Mukherjee
shayon.dev › post › 2024 › 225 › stop-relying-on-if-not-exists-for-concurrent-index-creation-in-postgresql
Stop Relying on IF NOT EXISTS for Concurrent Index Creation in PostgreSQL
August 12, 2024 - When you use `IF NOT EXISTS` and re-run your index creation, the task can silently complete while leaving behind an invalid index.
🌐
Neon
neon.com › postgresql › indexes › create-index
PostgreSQL CREATE INDEX Statement
CREATE INDEX [IF NOT EXISTS] index_name ON table_name(column1, column2, ...);
🌐
Hacker News
news.ycombinator.com › item
Don't rely on IF NOT EXISTS for concurrent index creation in PostgreSQL | Hacker News
August 14, 2024 - In my experience on large tables that are “busy”, sometimes indexes need to be added manually first from a utility session, perhaps inside tmux/screen that's detached from while they are created. This could take hours for large tables. Then once done, and the index is valid, an Active Record ...
🌐
Django Forum
forum.djangoproject.com › django internals › orm
Missing support for CREATE INDEX CONCURRENTLY IF NOT EXISTS - ORM - Django Forum
April 17, 2024 - Django doesn’t support the IF NOT EXISTS conditional when creating indexes concurrently in postgres. Has anyone else come across this? There is a ticket (just created) for that. Good to check if more people are interested in this: https://code.djangoproject.com/ticket/35383
Find elsewhere
🌐
PostgreSQL
postgresql.org › docs › current › indexes-unique.html
PostgreSQL: Documentation: 18: 11.6. Unique Indexes
May 14, 2026 - ... Unsupported versions: 13 / ... of the combined values of more than one column. CREATE UNIQUE INDEX name ON table (column [, ...]) [ NULLS [ NOT ] DISTINCT ];...
🌐
DBI Services
dbi-services.com › accueil › create index concurrently in postgresql
Create index CONCURRENTLY in PostgreSQL
November 22, 2017 - In PostgreSQL when you create an index on a table, sessions that want to write to the table must wait until the index build completed by default. There is a way around that, though, and in this post we’ll look at how you can avoid that. ... postgres=# \! cat a.sql drop table if exists t1; create table t1 ( a int, b varchar(50)); insert into t1 select a.*, md5(a::varchar) from generate_series(1,5000000) a; postgres=# \i a.sql DROP TABLE CREATE TABLE INSERT 0 5000000
🌐
GitHub
github.com › brianc › node-postgres › issues › 1862
CREATE INDEX IF NOT EXISTS throws an error · Issue #1862 · brianc/node-postgres
March 25, 2019 - when using CREATE INDEX IF NOT EXISTS, I get an error syntax error at or near "NOT" If I remove IF NOT EXISTS, it's working properly · This modifier was added in postgresql 9.5 · I'm using version 7.9.0 · For now, as a workaround I'm checking if err message contains already exists ·
Author   brianc
🌐
PostgreSQL
postgresql.org › docs › current › indexes-partial.html
PostgreSQL: Documentation: 18: 11.8. Partial Indexes
May 14, 2026 - It is also possible to allow only one null in a column by creating a unique partial index with an IS NULL restriction. Finally, a partial index can also be used to override the system's query plan choices. Also, data sets with peculiar distributions might cause the system to use an index when it really should not. In that case the index can be set up so that it is not available for the offending query. Normally, PostgreSQL makes reasonable choices about index usage (e.g., it avoids them when retrieving common values, so the earlier example really only saves index size, it is not required to avoid index usage), and grossly incorrect plan choices are cause for a bug report.
🌐
Supabase
supabase.com › docs › guides › database › managing indexes
Managing Indexes in Postgres | Supabase Docs
5 days ago - We might want to frequently query users based on their age: ... Without an index, Postgres will scan every row in the table to find equality matches on age. You can verify this by doing an explain on the query: ... It can take a long time to ...
🌐
Reddit
reddit.com › r/postgresql › how to create big indexes
r/PostgreSQL on Reddit: how to create big indexes
June 8, 2024 -

Hello experts,

We have a few tables having size ~5TB and are partitioned on a timestamp column. They have ~90 partitions in them and are storing 90 days of data. We want to create a couple of indexes on those tables. They are getting the incoming transactions(mainly inserts) 24/7 , which are mostly happening on the current day/live partition. Its RDS postgres version 15.4. So in this situation

Should we go with below i.e one time create index command on the table..

CREATE INDEX CONCURRENTLY idx1 ON tab(column_name);
Or
create index on individual partitions from different sessions, say for example create indexes on 30 partitions each from three different sessions so as to finish all the 90 partitions faster?
CREATE INDEX CONCURRENTLY idx1 ON tab_part1(column_name);
CREATE INDEX CONCURRENTLY idx1 ON tab_part2(column_name);..........

Basically I have three questions:
1)If we can do this index creation activity online without impacting the incoming transactions or do we have to take down time for this activity?
2)If we can't do it online then , what is the fastest method to do this index creation activity ?
3)Should we change the DB parameters in a certain way to make the process faster? We have currently set below parameters

max_parallel_workers-16
max_parallel_maintenance_workers-2
maintenance_work_mem- 4GB

Top answer
1 of 2
7
https://www.visuality.pl/posts/indexing-partitioned-table---postgres-stories Like this article (and the docs) say, I'd recommend creating an "invalid" index on the table as a whole using ONLY, then create the indexes concurrently on the partitions and attach one at a time. A bit slower, but much safer. You can use this query to generate the create index and alter index statements for the partitions SELECT nmsp_parent.nspname AS parent_schema, parent.relname AS parent, nmsp_child.nspname AS child_schema, child.relname AS child, 'CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_'|| child.relname || 'on_column_name ON ' || child.relname || '(column_name);' as "idx_create", 'ALTER INDEX idx_table_on_column_name ATTACH PARTITION idx' || child.relname || '_on_column_name;' as "idx_alter" FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace WHERE parent.relname='tablename';
2 of 2
1
We have a similar setup and stop the inserts when we have to create a new index. However that's not too often and the system can catch up afterwards. We have 1 partition a week that's about 2TB per partition We also create the partitions weeks in advance with all the indexes needed so they are ready to go when they become active and start getting inserts. Another big help is if you have a faster disk, you can create a new table space on that disk and have just the most used indexes on that faster disk. Then you can have weekly routines that move the indexes to different tablespaces so the active ones are always on the fast disk. Also, on your inserts, if you are using the copy command, you can get a huge performance increase by preprocessing the csv files so each one only has data for one partition, and then on the copy, have it go directly into correct partition table instead of the main/parent table.
🌐
PostgreSQL Tutorial
pgtutorial.com › home › postgresql tutorial › postgresql create index statement
PostgreSQL CREATE INDEX Statement
March 7, 2025 - The CREATE INDEX statement allows you to create an index on one or more columns of a table. Here’s the basic syntax of the CREATE INDEX statement: CREATE INDEX [IF NOT EXISTS] [index_name] ON tablename (column1, column2);Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
🌐
Medium
medium.com › dovetail-engineering › how-to-safely-create-unique-indexes-in-postgresql-e35980e6beb5
How to safely create unique indexes in PostgreSQL | by Dovetail Engineering | Dovetail Engineering | Medium
May 23, 2023 - In this case, our unique identifier is (document_id, object_id) which requires us to add a unique index on these two columns — so if we want to support upserts after creating that table, we need to add the corresponding index. 📚 An upsert is a piece of DB functionality that allows one to create a new database row or update an existing row based on a unique identifier where the identifier consists of one or more columns. In PostgreSQL, this is achieved by using ON CONFLICT DO UPDATE which requires us to have a unique index matching that identifier.
🌐
GitHub
github.com › duckdb › duckdb › issues › 11756
CREATE UNIQUE INDEX IF NOT EXISTS doesn't work any more · Issue #11756 · duckdb/duckdb
April 22, 2024 - What happens? CREATE UNIQUE INDEX IF NOT EXISTS works as expected on 0.9.2, but throws Catalog Error on 0.10.1. Note: this only occurs with UNIQUE, otherwise it's okay 🤔 I'll concede that CREATE INDEX IF NOT EXISTS it isn't 'explicitly m...
Author   duckdb
🌐
PostgresAI
postgres.ai › blog › 20211103-three-cases-against-if-not-exists-and-if-exists-in-postgresql-ddl
Three cases against IF NOT EXISTS / IF EXISTS in Postgres DDL | PostgresAI
November 3, 2021 - For such time of mistakes, we do need to have an error in CI tests - bug IF EXISTS is going to "mask" the problem. As a result, automated testing is not going to catch the problem, and this wrong change has risks to be released. For large tables under load, it is recommended to use CREATE INDEX CONCURRENTLY – it is going to work longer that CREATE INEDEX but it won't cause downtime.
🌐
Hello Interview
hellointerview.com › home › system design › common problems › bitly
Design a URL Shortener Like Bitly | Hello Interview System Design in a Hurry
February 14, 2026 - This creates a tradeoff between uniqueness, shortness, and efficiency—making it difficult to optimize all three simultaneously. To handle collisions, implement a UNIQUE constraint on the short code column and retry with bounded attempts (e.g., max 3-5 retries) before falling back to a different strategy or returning an error. Upon saving to the database, we'll get an error if the short code already exists.
🌐
DbSchema
dbschema.com › blog › postgresql › postgresql create index guide with psql and dbschema | dbschema
PostgreSQL CREATE INDEX Guide with psql and DbSchema | DbSchema
May 16, 2023 - Learn how to create indexes in PostgreSQL using psql and DbSchema, including index options, limitations, and practical examples.