Page Content

Tutorials

What Is Constraints And Indexes in PostgreSQL With Example

Constraints And Indexes in PostgreSQL

The fundamental tools used by PostgreSQL to maintain data integrity and enhance query performance are constraints and indexes, respectively. By limiting the kinds of data that can be put in tables and producing errors when specified requirements are broken during data manipulation operations, constraints help to ensure validity and consistency. PRIMARY KEY, which uniquely identifies each row and combines unique and not-null constraints, UNIQUE, which ensures that all values in a defined column or collection of columns are distinct, and NOT NULL, which prohibits columns from containing null values, are examples of common constraint types.

In order to enforce these UNIQUE and PRIMARY KEY criteria, PostgreSQL automatically generates a unique B-tree index. Indexing the foreign key column is frequently advised for performance reasons, and FOREIGN KEY requirements preserve referential integrity by connecting data in one table to those in a referenced table. While EXCLUSION constraints, which are commonly implemented using GiST indexes, restrict conflicting data entries, such as overlapping time ranges, CHECK constraints allow the formulation of Boolean expressions that column values must satisfy.

Constraints And Indexes
Constraints And Indexes

Constraints

The integrity, redundancy, and validity of the data in your tables are governed by constraints in PostgreSQL. Semantic business rules and those inherited from the relational model (such as domain, entity, and referential integrity) are two broad categories into which they can be divided. PostgreSQL may use rule systems, triggers, and procedural SQL languages to provide semantic constraints.

Multiple constraint kinds are supported by PostgreSQL:

NOT NULL Constraint: This straightforward constraint makes sure that a column isn’t allowed to have NULL values. It is more effective than a CHECK (column IS NOT NULL) constraint in PostgreSQL and is always a column constraint.

UNIQUE Constraint:This assures that every column or collection of columns has a unique value across the table. PostgreSQL automatically creates a unique B-tree index to impose unique constraints on unique fields. Partial indexes can also be used in conjunction with unique constraints to achieve conditional uniqueness.

PRIMARY KEY Constraint: Columns or combinations of columns uniquely identify each database entry as the main key. UNIQUE and Implicit NOT NULL enforcement.PostgreSQL automatically creates a B-tree index for each table’s main key.

FOREIGN KEY Constraint: Foreign keys link column values in two tables for referential integrity. No foreign keys like primary or unique constraints are indexed in PostgreSQL, therefore query optimisation is manual. Using ON UPDATE CASCADE, ON DELETE RESTRICT, CASCADE, SET NULL, or SET DEFAULT carefully alters foreign key behaviour. Lacking foreign key integration, table inheritance is challenging.

CHECK Constraint: A Boolean expression that must hold true for the data in a column or collection of columns can be specified using the most general constraint type, the CHECK constraint. CHECK restrictions can be specified at the table or column level. PostgreSQL’s query planner optimises queries by discarding partitions that don’t match CHECK restrictions. PostgreSQL’s CHECK constraints prevent reference data outside the current row for integrity, unlike UNIQUE, EXCLUDE, and FOREIGN KEY constraints.

EXCLUSION Constraint: PostgreSQL 9.0 introduced the EXCLUSION Constraint, which guarantees that at least one comparison of any two rows on defined fields or expressions using given operators would return false or null. Usually enforced with GiST indexes, they are especially helpful in avoiding overlaps in data kinds, such as range types, like booking schedules.

Indexes

The ability of indexes to dynamically map search predicates to sequences of tuple IDs (row identifiers) from a certain table significantly improves database speed by enabling the server to find and retrieve particular rows with ease. They are physically distinct entities that are internally ordered by one or more columns that hold pointers to the real data rows. They are also different from the main table data (heap files). WHERE and JOIN columns are not automatically indexed by PostgreSQL. SELECT, UPDATE, and DELETE operations speed up, but index maintenance slows INSERT. Unused indices ought to be eliminated.

Each index type also referred to as an access method supported by PostgreSQL is appropriate for a particular workload and kind of data. An internal slotted-page format is used by all index kinds.

B-tree Index: PostgreSQL’s default index type is the B-tree Index. The B-link tree implementation by Lehman and Yao serves as its foundation. Excellent for equality and range queries on data that can be sorted, and it can handle IS NULL or IS NOT NULL circumstances. With the help of ORDER BY and LIMIT clauses, data can be retrieved in a sorted order, possibly eliminating the need for a separate sort phase. The largest concurrency among index types without deadlock situations is offered by B-tree indexes.

Hash Index: Linear hashing is implemented using the hash index. Mostly helpful for straightforward equality operations. Previously discouraged due to poor performance and no write-ahead logging. Replication, WAL support, and performance improvements were added to PostgreSQL 10 and 11. B-trees typically still provide equivalent or superior lookup performance with less overhead in spite of this.

Generalized Search Tree (GiST) Index: Specialised indexing techniques can be implemented using the Generalised Search Tree (GiST) Index, an extendable balanced tree structure with various operations that are not preset. Applications include full-text search (e.g., R-trees), multidimensional cubes, and indexing complicated data types like geometric data. Able to maximise “nearest-neighbor” searches.

GiST indexes have the potential to be “lossy,” which means that they may return a superset of matched tuples and necessitate a recheck of the real table rows. Lookups may take longer, but updates for dynamic data are typically quicker than with GIN. For certain operator classes, index-only scans are supported.

Space-Partitioned Generalized Search Tree (SP-GiST) Index: Like GiST, this index type offers a foundation for a variety of disk-based data structures that are not balanced, including radix trees, quadtrees, and k-d trees. With support for range, text, and geometric data types, it can outperform GiST for some data sizes. The “nearest-neighbor” search function is also supported. For certain operator classes, index-only scans are supported.

Generalized Inverted Index (GIN) Index: Full-text search frequently uses these “inverted indexes” because they work well with data values that include numerous component values, such arrays and JSONB data. Effective when used in queries that check for the existence of particular component values. GIN index lookups often take up less storage space than GiST, but they are more costly (take longer to create and update). Because GIN indexes only retain lexemes for tsvector types, they may be lossy if the query contains weights, requiring a table row to be rechecked. The fact that each index entry typically contains only a portion of the original data value means that GIN indexes are generally unable to support index-only scans.

Block Range Index (BRIN) Index: These indexes hold summaries of values that appear in successive physical block ranges in a table. Offers a compromise between index size and search efficiency for big, clustered tables, and works best for columns whose values are closely tied to the physical arrangement of table rows.

Code Example:

-- Create a sample table with a timestamp column
CREATE TABLE sensor_data (
    id SERIAL PRIMARY KEY,
    reading_time TIMESTAMP,
    temperature NUMERIC
);
-- Insert a large number of rows
INSERT INTO sensor_data (reading_time, temperature)
SELECT
    '2024-01-01'::TIMESTAMP + (n || ' minutes')::INTERVAL,
    (random() * 50)::NUMERIC
FROM generate_series(1, 1000000) AS n;
-- Create a BRIN index on the reading_time column
-- BRIN indexes work best when data is naturally ordered (e.g., by insertion time)
CREATE INDEX idx_sensor_data_time_brin ON sensor_data USING BRIN(reading_time);
EXPLAIN ANALYZE
SELECT COUNT(*)
FROM sensor_data
WHERE reading_time > '2024-04-01' AND reading_time < '2024-04-02';

Output:

CREATE TABLE
INSERT 0 1000000
CREATE INDEX
                                                                                QUERY PLAN                                                                                 
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=14608.42..14608.43 rows=1 width=8) (actual time=42.023..48.340 rows=1 loops=1)
   ->  Gather  (cost=14608.21..14608.42 rows=2 width=8) (actual time=42.011..48.330 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=13608.21..13608.22 rows=1 width=8) (actual time=34.445..34.448 rows=1 loops=3)
               ->  Parallel Seq Scan on sensor_data  (cost=0.00..13603.00 rows=2083 width=0) (actual time=12.418..34.161 rows=480 loops=3)
                     Filter: ((reading_time > '2024-04-01 00:00:00'::timestamp without time zone) AND (reading_time < '2024-04-02 00:00:00'::timestamp without time zone))
                     Rows Removed by Filter: 332854
 Planning Time: 0.155 ms
 Execution Time: 48.394 ms
(10 rows)

Other Index Variations

Multicolumn Indexes: Index types offered by B-tree, GiST, GIN, and BRIN include multicolumn indexes, which are defined on up to 32 columns. With B-tree indexes, where leading columns are most useful for limiting scans, column order is critical for efficiency.

Covering Indexes: Generally referred to as index-only plans, a covering index has extra attributes not included in the search key, enabling the index to answer queries without consulting the underlying database. This can greatly improve performance. At the moment, covering indexes are only supported by B-tree indexes.

Indexes on Expressions: Creating indexes on arbitrary scalar expressions of columns is possible with PostgreSQL, not just the columns themselves (e.g., lower(column) for case-insensitive searches).

Partial Indexes: These indexes only include material that satisfies a particular WHERE criterion. They may be speedier and use less disc space because more of the index may fit in RAM.

Index Management

Creation: Create an index with build INDEX.Despite its restrictions and complexity, live systems should use CREATE INDEX CONCURRENTLY to avoid blocking INSERT, UPDATE, and DELETE operations while building the index.

Maintenance: Indexes can become “bloated” or invalid, requiring REINDEX rebuilding to boost up query performance. CREATE INDEX CONCURRENTLY is preferred for live systems because it allows concurrent data manipulation operations (INSERT, UPDATE, DELETE) to continue during index construction, avoiding blocking. However, it is more complicated and has specific caveats about index failures and invalid indexes.

Monitoring: To learn how the query planner uses indexes and assesses queries, the EXPLAIN command is essential. Runtime statistics are provided via options like ANALYSE and BUFFERS. Index utilisation and I/O statistics are shown by pg stat all indexes.

Conclusion

In PostgreSQL, constraints and indexes work together to make sure that data is safe and fast. Constraints like NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK, and EXCLUSION make sure that recorded data is accurate, consistent, and reliable by making sure that there are no invalid or conflicting entries at the database level. Indexes, on the other hand, speed up query performance by making it easier to find data, sort it, and decrease I/O overhead.

Examples of indexes include B-tree, Hash, GiST, SP-GiST, GIN, BRIN, and specialised types like partial and covering indexes. These methods work together to make a strong platform for scalable database design. Constraints keep information valid, while indexes make sure that even big datasets can be easily accessed and kept up to date. It is important to find the right balance between these tools. Too many indexes can slow down writes, and not enough restrictions can let bad data in. This is why careful schema planning is important for any PostgreSQL system that works properly.

Kowsalya
Kowsalya
Hi, I'm Kowsalya a B.Com graduate and currently working as an Author at Govindhtech Solutions. I'm deeply passionate about publishing the latest tech news and tutorials that bringing insightful updates to readers. I enjoy creating step-by-step guides and making complex topics easier to understand for everyone.
Index