Indexes & Foreign Keys
Indexes
Section titled “Indexes”Indexes improve query performance by allowing the database to find rows faster.
Index Properties
Section titled “Index Properties”| Property | Description |
|---|---|
| Name | Auto-generated: {table}_{columns}[_unique]_index |
| Unique | Enforces uniqueness across indexed columns |
| Order | Sort order: ASC (ascending) or DESC (descending) |
| Columns | One or more columns (composite index) |
| Where | Partial index condition (optional) |
| Primary Key | Indicates this is the primary key index (read-only) |
Index Types
Section titled “Index Types”| Type | Description | Example Use Case |
|---|---|---|
| Single-column | Index on one column | userId for user lookups |
| Composite | Index on multiple columns | (userId, createdOn) for filtering |
| Unique | Prevents duplicate values | Email addresses |
| Partial | Index only rows matching a WHERE condition | Active records only |
| Primary Key | Unique identifier for each row (cannot be deleted) | id column |
Single-Column Index
Section titled “Single-Column Index”Use case: Looking up users by email
Table: userColumn: emailUnique: trueGenerated name: user_email_unique_index
This speeds up queries like:
SELECT * FROM "user" WHERE "email" = 'john@example.com'Composite Index
Section titled “Composite Index”Use case: Filtering orders by user and date range
Table: orderColumns: userId, createdOnOrder: ASC, DESCGenerated name: order_userId_createdOn_index
This speeds up queries like:
SELECT * FROM "order"WHERE "userId" = 123AND "createdOn" > '2024-01-01'ORDER BY "createdOn" DESCUnique Index
Section titled “Unique Index”Use case: Preventing duplicate email addresses
Table: userColumn: emailUnique: trueThis enforces uniqueness at the database level, preventing duplicate registrations.
Partial Index
Section titled “Partial Index”Use case: Indexing only active users
Table: userColumn: lastNameWhere: "isActive" = trueThis creates a smaller, faster index that only includes active users.
Partial Index Examples
Section titled “Partial Index Examples”Use the Where Clause field to create partial indexes:
Index only active users:
"isActive" = trueIndex only recent orders:
"createdOn" > '2024-01-01'Index non-null emails:
"email" IS NOT NULLIndex pending and processing orders:
"status" IN ('PENDING', 'PROCESSING')Index high-value orders:
"total" > 1000Index Naming Convention
Section titled “Index Naming Convention”Indexes are automatically named using this pattern:
{tableName}_{column1}_{column2}_[unique_]indexExamples:
user_email_unique_indexorder_userId_createdOn_indexproduct_sku_unique_indexorderItem_orderId_index
When to Add Indexes
Section titled “When to Add Indexes”Always Index
Section titled “Always Index”- Primary keys (automatic)
- Foreign keys (automatic with
*Idnaming) - Unique constraints (email, username)
- Columns used in WHERE clauses frequently
Consider Indexing
Section titled “Consider Indexing”- Columns used in JOIN conditions
- Columns used in ORDER BY
- Columns used in GROUP BY
- Columns with high selectivity (many unique values)
Avoid Indexing
Section titled “Avoid Indexing”- Small tables (< 1000 rows)
- Columns with low selectivity (few unique values)
- Columns that are rarely queried
- Tables with high write volume (indexes slow down writes)
Foreign Keys
Section titled “Foreign Keys”Foreign keys enforce referential integrity between tables.
Foreign Key Properties
Section titled “Foreign Key Properties”| Property | Description |
|---|---|
| Name | Auto-generated: {table}_{column}_{refTable}_{refColumn}_fk |
| Column | The column in your table (must be BIGINT/BIGSERIAL) |
| Ref Table | The table being referenced |
| Ref Column | The column being referenced (typically id) |
| On Delete | Action when the referenced row is deleted |
| On Update | Action when the referenced row is updated |
Foreign Key Actions
Section titled “Foreign Key Actions”| Action | Description | Use Case |
|---|---|---|
NO ACTION | Prevent delete/update if references exist (default) | Preserve data integrity |
RESTRICT | Like NO ACTION, but checked immediately | Strict integrity enforcement |
CASCADE | Automatically delete/update all referencing rows | Dependent data (e.g., order items) |
SET NULL | Set the foreign key column to NULL | Optional relationships |
SET DEFAULT | Set the foreign key column to its default value | Fallback to default reference |
When to Use Each Action
Section titled “When to Use Each Action”NO ACTION (default):
- Most relationships
- When you want to prevent accidental deletions
- When child records should be explicitly handled
CASCADE:
- Parent-child relationships where children are meaningless without parent
- Example: Delete order → delete all order items
- Example: Delete user → delete all user sessions
SET NULL:
- Optional relationships
- Example: Delete manager → set employee.managerId to NULL
- Example: Delete category → set product.categoryId to NULL
RESTRICT:
- When you need immediate constraint checking
- Rare in practice (NO ACTION is usually sufficient)
Common Foreign Key Patterns
Section titled “Common Foreign Key Patterns”User → Orders (CASCADE delete)
Section titled “User → Orders (CASCADE delete)”Table: orderColumn: userIdRef Table: userRef Column: idOn Delete: CASCADEOn Update: NO ACTIONWhen a user is deleted, all their orders are automatically deleted.
Order → OrderItems (CASCADE delete)
Section titled “Order → OrderItems (CASCADE delete)”Table: orderItemColumn: orderIdRef Table: orderRef Column: idOn Delete: CASCADEOn Update: NO ACTIONWhen an order is deleted, all its items are automatically deleted.
Optional Manager Reference (SET NULL)
Section titled “Optional Manager Reference (SET NULL)”Table: employeeColumn: managerIdRef Table: employeeRef Column: idOn Delete: SET NULLOn Update: NO ACTIONWhen a manager is deleted, their employees’ managerId is set to NULL.
Product → Category (NO ACTION)
Section titled “Product → Category (NO ACTION)”Table: productColumn: categoryIdRef Table: categoryRef Column: idOn Delete: NO ACTIONOn Update: NO ACTIONCannot delete a category if products still reference it.
Relationship Examples
Section titled “Relationship Examples”One-to-Many: User → Orders
Section titled “One-to-Many: User → Orders”User table:
id: BIGSERIAL, primary keyfirstName: VARCHAR(30)email: VARCHAR(255), uniqueOrder table:
id: BIGSERIAL, primary keyuserId: BIGINT, foreign key to user.idtotal: DECIMAL(10,2)status: ENUM('PENDING','COMPLETED')Foreign key:
order.userId → user.idOn Delete: CASCADEOne user can have many orders. Deleting a user deletes all their orders.
One-to-Many: Order → OrderItems
Section titled “One-to-Many: Order → OrderItems”Order table:
id: BIGSERIAL, primary keyuserId: BIGINTtotal: DECIMAL(10,2)OrderItem table:
id: BIGSERIAL, primary keyorderId: BIGINT, foreign key to order.idproductId: BIGINT, foreign key to product.idquantity: INTEGERprice: DECIMAL(10,2)Foreign keys:
orderItem.orderId → order.id (CASCADE)orderItem.productId → product.id (NO ACTION)One order can have many items. Deleting an order deletes all its items.
Many-to-Many: Users ↔ Roles
Section titled “Many-to-Many: Users ↔ Roles”User table:
id: BIGSERIAL, primary keyfirstName: VARCHAR(30)email: VARCHAR(255)Role table:
id: BIGSERIAL, primary keyname: VARCHAR(50)description: TEXTUserRole junction table:
id: BIGSERIAL, primary keyuserId: BIGINT, foreign key to user.idroleId: BIGINT, foreign key to role.idForeign keys:
userRole.userId → user.id (CASCADE)userRole.roleId → role.id (CASCADE)Unique composite index:
Columns: userId, roleIdUnique: trueThis prevents duplicate role assignments and enables efficient lookups.
Self-Referencing: Employee → Manager
Section titled “Self-Referencing: Employee → Manager”Employee table:
id: BIGSERIAL, primary keyfirstName: VARCHAR(30)managerId: BIGINT, nullable, foreign key to employee.idForeign key:
employee.managerId → employee.idOn Delete: SET NULLAn employee can have a manager (another employee). Deleting a manager sets their employees’ managerId to NULL.
Performance Optimization
Section titled “Performance Optimization”Composite Index Strategy
Section titled “Composite Index Strategy”For queries that filter by multiple columns, create composite indexes with the most selective column first:
Good:
Index: (userId, createdOn)Query: WHERE userId = 123 AND createdOn > '2024-01-01'Less optimal:
Index: (createdOn, userId)Query: WHERE userId = 123 AND createdOn > '2024-01-01'Covering Indexes
Section titled “Covering Indexes”Include all columns needed by a query in the index to avoid table lookups:
Index: (userId, createdOn, status)Query: SELECT status FROM order WHERE userId = 123 ORDER BY createdOnThe database can satisfy this query entirely from the index.
Index Maintenance
Section titled “Index Maintenance”- Monitor index usage with database analytics
- Remove unused indexes (they slow down writes)
- Rebuild indexes periodically on high-traffic tables
- Use partial indexes for large tables with filtered queries
Best Practices
Section titled “Best Practices”Indexes
Section titled “Indexes”- Index foreign keys – Speeds up joins (automatic with
*Idnaming) - Index unique constraints – Enforces uniqueness and speeds up lookups
- Use composite indexes – For multi-column queries
- Consider partial indexes – For large tables with filtered queries
- Monitor performance – Add indexes based on actual query patterns
Foreign Keys
Section titled “Foreign Keys”- Always use foreign keys – Enforces referential integrity
- Choose appropriate actions – CASCADE for dependent data, NO ACTION for independent data
- Use SET NULL for optional relationships – Allows parent deletion without losing children
- Document relationships – Add comments explaining the relationship
- Test cascade behavior – Ensure deletions cascade as expected