Initial commit
This commit is contained in:
309
skills/erd-skill/best-practices.md
Normal file
309
skills/erd-skill/best-practices.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# Database Design Best Practices
|
||||
|
||||
## Normalization
|
||||
|
||||
### First Normal Form (1NF)
|
||||
- Each column contains atomic (indivisible) values
|
||||
- Each column contains values of a single type
|
||||
- Each column has a unique name
|
||||
- The order of rows doesn't matter
|
||||
|
||||
### Second Normal Form (2NF)
|
||||
- Meets all requirements of 1NF
|
||||
- No partial dependencies (all non-key attributes fully depend on the primary key)
|
||||
- Relevant for composite primary keys
|
||||
|
||||
### Third Normal Form (3NF)
|
||||
- Meets all requirements of 2NF
|
||||
- No transitive dependencies (non-key attributes don't depend on other non-key attributes)
|
||||
- Most common target normalization level
|
||||
|
||||
### Denormalization
|
||||
Consider denormalization for:
|
||||
- Read-heavy workloads where query performance is critical
|
||||
- Aggregated data that's expensive to compute
|
||||
- Historical snapshots that shouldn't change
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Tables
|
||||
- Use plural nouns (e.g., `users`, `posts`, `orders`)
|
||||
- Use snake_case for multi-word names (e.g., `user_profiles`, `order_items`)
|
||||
- Keep names descriptive but concise
|
||||
|
||||
### Columns
|
||||
- Use singular nouns (e.g., `id`, `name`, `email`)
|
||||
- Use snake_case for multi-word names (e.g., `created_at`, `user_id`)
|
||||
- Boolean columns should use prefixes like `is_`, `has_`, `can_` (e.g., `is_active`, `has_verified_email`)
|
||||
|
||||
### Foreign Keys
|
||||
- Use format: `{referenced_table_singular}_id` (e.g., `user_id`, `post_id`)
|
||||
- Be consistent across the entire schema
|
||||
|
||||
### Indexes
|
||||
- Name format: `idx_{table}_{columns}` or `{table}_{columns}_idx`
|
||||
- Include purpose when relevant: `idx_users_email_unique`, `idx_posts_author_created`
|
||||
|
||||
## Primary Keys
|
||||
|
||||
### Auto-incrementing Integers
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key, increment]
|
||||
}
|
||||
```
|
||||
**Pros:** Simple, compact, sequential, human-readable
|
||||
**Cons:** Exposes record count, potential security concern, not globally unique
|
||||
|
||||
### UUIDs
|
||||
```dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
```
|
||||
**Pros:** Globally unique, can be generated client-side, harder to enumerate
|
||||
**Cons:** Larger storage, less human-readable, non-sequential (worse for indexing)
|
||||
|
||||
### Composite Keys
|
||||
```dbml
|
||||
Table user_roles {
|
||||
user_id integer
|
||||
role_id integer
|
||||
|
||||
indexes {
|
||||
(user_id, role_id) [primary key]
|
||||
}
|
||||
}
|
||||
```
|
||||
**Use when:** Natural composite identifier exists and makes sense
|
||||
|
||||
## Indexing Strategies
|
||||
|
||||
### Single-column Indexes
|
||||
Create indexes on columns frequently used in:
|
||||
- WHERE clauses
|
||||
- JOIN conditions
|
||||
- ORDER BY clauses
|
||||
- Foreign keys
|
||||
|
||||
### Composite Indexes
|
||||
- Order matters: most selective column first
|
||||
- Consider query patterns when designing
|
||||
- Can satisfy multiple query types
|
||||
|
||||
```dbml
|
||||
Table posts {
|
||||
id integer [primary key]
|
||||
author_id integer
|
||||
status varchar
|
||||
created_at timestamp
|
||||
|
||||
indexes {
|
||||
(author_id, created_at) [name: 'idx_posts_author_created']
|
||||
status [name: 'idx_posts_status']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Unique Indexes
|
||||
Use for:
|
||||
- Ensuring data uniqueness (e.g., email, username)
|
||||
- Natural keys
|
||||
- Business constraints
|
||||
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
email varchar [unique]
|
||||
username varchar [unique]
|
||||
}
|
||||
```
|
||||
|
||||
## Relationships
|
||||
|
||||
### One-to-Many
|
||||
Most common relationship type. Use foreign key in the "many" table.
|
||||
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
}
|
||||
|
||||
Table posts {
|
||||
id integer [primary key]
|
||||
author_id integer [ref: > users.id]
|
||||
}
|
||||
```
|
||||
|
||||
### Many-to-Many
|
||||
Requires junction/join table.
|
||||
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
}
|
||||
|
||||
Table roles {
|
||||
id integer [primary key]
|
||||
}
|
||||
|
||||
Table user_roles {
|
||||
user_id integer [ref: > users.id]
|
||||
role_id integer [ref: > roles.id]
|
||||
|
||||
indexes {
|
||||
(user_id, role_id) [primary key]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### One-to-One
|
||||
Less common. Can be modeled with unique foreign key or by merging tables.
|
||||
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
}
|
||||
|
||||
Table user_profiles {
|
||||
id integer [primary key]
|
||||
user_id integer [ref: - users.id, unique]
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints and Validation
|
||||
|
||||
### NOT NULL
|
||||
Use for required fields:
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
email varchar [not null]
|
||||
username varchar [not null]
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values
|
||||
Provide sensible defaults:
|
||||
```dbml
|
||||
Table posts {
|
||||
id integer [primary key]
|
||||
status varchar [default: 'draft']
|
||||
created_at timestamp [default: `now()`]
|
||||
}
|
||||
```
|
||||
|
||||
### Check Constraints
|
||||
Enforce business rules at the database level:
|
||||
```dbml
|
||||
Table products {
|
||||
id integer [primary key]
|
||||
price decimal [note: 'CHECK (price >= 0)']
|
||||
stock integer [note: 'CHECK (stock >= 0)']
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Timestamps
|
||||
If tracking creation and update times has business value:
|
||||
```dbml
|
||||
Table posts {
|
||||
id integer [primary key]
|
||||
// ... other fields
|
||||
created_at timestamp [not null, default: `now()`]
|
||||
updated_at timestamp [not null, default: `now()`]
|
||||
}
|
||||
```
|
||||
|
||||
### Soft Deletes
|
||||
For preserving deleted records:
|
||||
```dbml
|
||||
Table users {
|
||||
id integer [primary key]
|
||||
// ... other fields
|
||||
deleted_at timestamp [null]
|
||||
}
|
||||
```
|
||||
|
||||
### Polymorphic Associations
|
||||
When a table can belong to multiple parent types:
|
||||
```dbml
|
||||
Table comments {
|
||||
id integer [primary key]
|
||||
commentable_type varchar // 'Post' or 'Product'
|
||||
commentable_id integer // ID of Post or Product
|
||||
content text
|
||||
}
|
||||
```
|
||||
|
||||
### Audit Trail
|
||||
Track changes to important data:
|
||||
```dbml
|
||||
Table order_history {
|
||||
id integer [primary key]
|
||||
order_id integer [ref: > orders.id]
|
||||
changed_by integer [ref: > users.id]
|
||||
old_status varchar
|
||||
new_status varchar
|
||||
changed_at timestamp [default: `now()`]
|
||||
}
|
||||
```
|
||||
|
||||
### Versioning
|
||||
For maintaining version history:
|
||||
```dbml
|
||||
Table documents {
|
||||
id integer [primary key]
|
||||
version integer [not null, default: 1]
|
||||
// ... other fields
|
||||
|
||||
indexes {
|
||||
(id, version) [unique]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Avoid Over-indexing
|
||||
- Each index adds write overhead
|
||||
- Maintain only indexes that are actually used
|
||||
- Monitor query performance and adjust
|
||||
|
||||
### Use Appropriate Data Types
|
||||
- VARCHAR vs TEXT: Use VARCHAR with appropriate length
|
||||
- INT vs BIGINT: Choose based on expected range
|
||||
- DECIMAL for money: Avoid floating point for currency
|
||||
- TIMESTAMP vs DATE: Use appropriate precision
|
||||
|
||||
## Schema Organization
|
||||
|
||||
### Use Table Groups
|
||||
Organize related tables:
|
||||
```dbml
|
||||
TableGroup user_management {
|
||||
users
|
||||
user_profiles
|
||||
user_roles
|
||||
}
|
||||
|
||||
TableGroup content {
|
||||
posts
|
||||
comments
|
||||
tags
|
||||
}
|
||||
```
|
||||
|
||||
### Use Schemas/Namespaces
|
||||
For large applications:
|
||||
```dbml
|
||||
Table auth.users {
|
||||
id integer [primary key]
|
||||
}
|
||||
|
||||
Table cms.posts {
|
||||
id integer [primary key]
|
||||
author_id integer [ref: > auth.users.id]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user