Files
gh-byunk-claude-code-toolki…/skills/erd-skill/best-practices.md
2025-11-29 18:02:57 +08:00

310 lines
6.2 KiB
Markdown

# 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]
}
```