A composite unique index normally treats NULL values as distinct. An inventory table keyed by product, warehouse, and area can therefore accept several rows whose area is NULL, even when those rows represent the same logical record. section 2.8
The upsert that inserts duplicates
With a default unique index on (product_id, warehouse_id, area), an upsert containing area = NULL can insert another row instead of updating the existing one. Repeating the operation with quantities 5 and 7 produces separate NULL-area rows, creating data-integrity problems. section 2.8
PostgreSQL 15 adds NULLS NOT DISTINCT to unique indexes. This option makes NULL values compare as equal for uniqueness enforcement, so matching non-NULL key values identify the same row during ON CONFLICT processing. section 2.8
DELETE FROM erp.inventory WHERE quantity = 7; DROP INDEX erp.inventory_product_id_warehouse_id_area_idx; CREATE UNIQUE INDEX ON erp.inventory (product_id, warehouse_id, area) NULLS NOT DISTINCT;
Rebuild the unique index with NULLS NOT DISTINCT, then retry the upsert; the existing NULL-area row is updated rather than duplicated. section 2.8
Although this option fixes the immediate upsert behavior, nullable columns in composite unique keys remain a questionable design. Prefer a meaningful sentinel value such as common_area or restructure the schema when that better represents the data. section 2.11 section 2.8
