Seems like a decent approach. Of course, one should apply some human verification to this before automatically dropping everything that seems unused. For example, it's conceivable that the statistics were recently reset and/or an index is only used for some occasional batch tasks.

Answer from Peter Eisentraut on Stack Exchange
🌐
Medium
medium.com › @anasanjaria › how-to-determine-unused-index-in-postgresql-6af846686a3
How to determine unused index in PostgreSQL? | Medium
November 23, 2025 - Hence, the most undesirable type of index is the one that remains unused. Another reason is minimalism. In software engineering, the principle of minimalism works the best. If it’s not used, what’s the point of keeping them. PostgreSQL has a view named pg_stat_user_indexescontaining index ...
🌐
pgDash
pgdash.io › blog › finding-unused-indexes-in-postgresql.html
Finding Unused Indexes In PostgreSQL - pgDash
Unused indexes are indexes that are not accessed by Postgres to execute any query. Are they bad?
Discussions

postgresql - Find unused indexes - Database Administrators Stack Exchange
I'm using the following query to find unused indexes: SELECT PSUI.indexrelid::regclass AS IndexName ,PSUI.relid::regclass AS TableName FROM pg_stat_user_indexes AS PSUI JOIN pg_index AS PI ON... More on dba.stackexchange.com
🌐 dba.stackexchange.com
sql - PostgreSQL Index Usage Analysis - Stack Overflow
Is there a tool or method to analyze Postgres, and determine what missing indexes should be created, and which unused indexes should be removed? I have a little experience doing this with the "prof... More on stackoverflow.com
🌐 stackoverflow.com
sql - Unused index in PostgreSQL - Stack Overflow
I'm learning indexing in PostgreSQL now. I started trying to create my index and analyzing how it will affect execution time. I created some tables with such columns: also, I filled them with data.... More on stackoverflow.com
🌐 stackoverflow.com
postgresql - Delete unused indexes - Stack Overflow
I run this query for check if there are some unused indexes in my DataBase. More on stackoverflow.com
🌐 stackoverflow.com
🌐
CYBERTEC PostgreSQL
cybertec-postgresql.com › home › get rid of your unused indexes!
index maintenance | Get rid of your unused indexes! | CYBERTEC PostgreSQL | Services & Support
March 5, 2024 - Using indexes comes at a cost that you should only pay if you really need them. Identify the unused indexes in your database and remove them.
🌐
PostgreSQL Wiki
wiki.postgresql.org › wiki › Index_Maintenance
Index Maintenance - PostgreSQL wiki
One of the common needs for a REINDEX is when indexes become bloated due to either sparse deletions or use of VACUUM FULL (with pre 9.0 versions). An estimator for the amount of bloat in a table has been included in the check_postgres script, which you can call directly or incorporate into ...
🌐
DEV Community
dev.to › pjhoberman › finding-unused-indexes-in-postgres-4k61
Finding Unused Indexes in Postgres - DEV Community
September 18, 2024 - The output will be the table name, index name, and some data about each index including how many times the index was used in a query, how many tuples (index rows in this case) were read from the index, and how many tuples were actually fetched after all filtering was complete.
🌐
DataCamp
datacamp.com › doc › postgresql › dropping-unused-indexes
PostgreSQL Dropping Unused Indexes
The first query helps identify indexes that have never been used (idx_scan = 0). Here, idx_scan represents the number of times the index has been used in a query. Replace idx_unused with the actual index name identified from the query:
Find elsewhere
🌐
Ei Innovations
eginnovations.com › documentation › PostgreSQL › PostgreSQL-Unused-Indexes.htm
PostgreSQL Unused Indexes Test
This test reports the number and names of unused/useless indexes, and thus prompts administrators to remove them so as to save the server from unnecessary performance degradations.
🌐
Medium
medium.com › @dmitry-naumenko › how-to-define-unused-indexes-in-postgresql-471da6f6f33f
How to identify unused indexes in PostgreSQL | by Dmitrii Naumenko | Medium
March 30, 2020 - This query displays a list of all indexes sorted by scan frequency (column №3). If some indexes have a scan frequency (index_scans_count) of zero, then you should consider deleting them.
🌐
Medium
medium.com › @jramcloud1 › 25-postgresql-17-performance-tuning-detecting-and-removing-unused-indexes-a6f01d1d9055
25 - PostgreSQL 17 Performance Tuning: Detecting and Removing Unused Indexes | by Jeyaram Ayyalusamy | Medium
September 9, 2025 - Unused indexes harm performance by slowing writes and wasting space. PostgreSQL 17 provides system views (pg_stat_user_indexes) to spot them.
🌐
PostgresAI
postgres.ai › performance & optimization › indexing › find unused indexes
How to find unused indexes | PostgresAI
As a result, build a list of indexes that can be reliably named as "unused" – we know that we didn't use them during a significant time, neither on the primary nor on replicas, on all production systems we can observe. For each index in the list drop it using DROP INDEX CONCURRENTLY. ... select stats_reset, age(now(), stats_reset) from pg_stat_database where datname = current_database(); For holistic analysis, you can use this query from postgres-checkup, which also includes the analysis of rarely used indexes, DDL commands for index cleanup, and presents the result as JSON for easier integration in observability and automation systems:
🌐
Hakibenita
hakibenita.com › postgresql-unused-index-size
The Unexpected Find That Freed 20GB of Unused Index Space | Haki Benita
February 1, 2021 - We just shaved off more than 760MB of unused indexed tuples without compromising performance! ... Once we had a good experience with one partial index, we figured we might have more indexes like that. To find good candidates for partial index we wrote a query to search for indexes on fields with high null_frac, the percent of values of the column that PostgreSQL estimates are NULL:
🌐
pganalyze
pganalyze.com › docs › checks › schema › index_unused
Unused Indexes · pganalyze
Connect Claude Code, Codex & Cursor to your Postgres statistics: pganalyze MCP Server is now in Public Preview ... Detects indexes that are not in use by any queries within the last 35 days and creates an issue with severity "info", one for each table (or table hierarchy in case of inheritance ...
Top answer
1 of 2
3

From "Rows removed by filter: 125" I see there are too few rows in the events table. Just add couple of thousands rows and give it another go

from the docs

Use real data for experimentation. Using test data for setting up indexes will tell you what indexes you need for the test data, but that is all.

It is especially fatal to use very small test data sets. While selecting 1000 out of 100000 rows could be a candidate for an index, selecting 1 out of 100 rows will hardly be, because the 100 rows probably fit within a single disk page, and there is no plan that can beat sequentially fetching 1 disk page.

In most cases, when database using an index it gets only address where the row is located. It contains data block_id and the offset because there might be many rows in one block of 4 or 8 Kb.

So, the database first searches index for the block adress, then it looks for the block on disk, reads it and parses the line you need.

When there are too few rows they fit into one on in couple of data blocks which makes it easier and quicker for DB to read whole table without using index at all.

2 of 2
0

See it the following way: The database decides which way is faster to find your tuple (=record) with organizer_id 4. There are two ways: a) Read the index and then skip to the block which contains the data. b) Read the heap and find the record there.

The information in your screenshot show 126 records (125 skipped + your record) with a length ("width") of 62 bytes. Including overhead these data fit into two database blocks of 8 KB. As a rotating disk or SSD reads a series of blocks anyway - they read always more blocks into the buffer - it's one read operation for these two blocks.

So the database decides that it is pointless to read first the index to find the correct record (of in our case two blocks) and then read the data from the heap with the information from the index. That would be two read operations. Even with modern technology newer than rotating disks this needs more time than just scanning the two blocks. That's why the database doesn't use the index.

Indexes on such small tables aren't good for searching. Nevertheless unique indexes avoid double entries.

🌐
Stormatics
stormatics.tech › home › unused indexes in postgresql: risks, detection, and safe removal
Unused Indexes In PostgreSQL: Risks, Detection, And Safe Removal - Stormatics
March 30, 2026 - A large index can appear unused in statistics, but must not be dropped if it enforces a PRIMARY, UNIQUE, or FOREIGN KEY constraint. PostgreSQL uses these indexes to guarantee data integrity and will not allow them to be dropped unless the constraint ...
🌐
Medium
tech-talk.the-experts.nl › your-postgres-indexes-might-be-useless-heres-how-to-check-e1314fd1b6ae
Your Postgres Indexes Might Be Useless - the/experts Tech Talk
March 4, 2026 - Learn how to find unused, oversized, and redundant Postgres indexes using pg_stat_user_indexes, pg_stat_statements, and HypoPG. Real data, 200M rows.
Top answer
1 of 2
8

Your query misses some uses of indexes that do not require them to be scanned:

  • they enforce primary key, unique and exclusion constraints

  • they influence statistics collection (for “expression indexes”)

Here is my gold standard query from my blog post:

SELECT s.schemaname,
       s.relname AS tablename,
       s.indexrelname AS indexname,
       pg_relation_size(s.indexrelid) AS index_size
FROM pg_catalog.pg_stat_user_indexes s
   JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid
WHERE s.idx_scan = 0      -- has never been scanned
  AND 0 <>ALL (i.indkey)  -- no index column is an expression
  AND NOT EXISTS          -- does not enforce a constraint
         (SELECT 1 FROM pg_catalog.pg_constraint c
          WHERE c.conindid = s.indexrelid)
ORDER BY pg_relation_size(s.indexrelid) DESC;

Anything that shows up there has not been used since the statistics have been reset and can be safely dropped.

There are a few caveats:

  • statistics collection must run (look for the “statistics collector” process and see if you have warnings about “stale statistics” in the log)

  • run the query against your production database

  • if your program is running at many sites, try it on all of them (different users have different usage patterns)

2 of 2
0

It is possible you can delete them, however you should make sure your query runs after a typical workload. That is, are there some indexes that show no usage in this query only used during certain times when specialized queries run? Month-end reporting, weekly runs, etc? We ran into this a couple of times - several large indexes didn't get used during the day but supported month-end summaries.

🌐
DEV Community
dev.to › dm8ry › identifying-unused-indexes-in-postgresql-a-bash-script-solution-2laj
Identifying Unused Indexes in PostgreSQL: A Bash Script Solution - DEV Community
October 29, 2024 - Once executed, the script outputs a summary of the databases processed and lists any unused indexes it finds. The final report helps database administrators make informed decisions about which indexes can be safely dropped. Regularly auditing indexes in your PostgreSQL databases is crucial for maintaining optimal performance.