
database-designer
by masanao-ohba
Manifests of the Sub Agents and Skills
SKILL.md
name: database-designer description: Designs database schemas and migrations for PHP/CakePHP applications with multi-tenant support
Database Designer
A specialized skill for designing database schemas, migrations, and multi-tenant architectures for PHP/CakePHP applications.
Core Responsibilities
1. Schema Design Principles
Normalization Levels:
-- 1NF: Atomic values, no repeating groups
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
order_date DATETIME,
-- Each column contains single value
);
-- 2NF: No partial dependencies
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id)
);
-- 3NF: No transitive dependencies
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(255),
category_id INT,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
2. CakePHP Migration Format
Migration File Structure:
<?php
declare(strict_types=1);
use Migrations\AbstractMigration;
/**
* Create Users table migration
*/
class CreateUsers extends AbstractMigration
{
/**
* Change Method.
*/
public function change(): void
{
// Create table
$table = $this->table('users');
// Add columns
$table->addColumn('email', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
])
->addColumn('password', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
])
->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
])
->addColumn('company_id', 'integer', [
'default' => null,
'null' => false,
])
->addColumn('status', 'integer', [
'default' => 1,
'null' => false,
])
->addColumn('del_flg', 'integer', [
'default' => 0,
'null' => false,
])
->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
])
->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
// Add indexes
$table->addIndex(['email'], [
'name' => 'idx_users_email',
'unique' => true,
])
->addIndex(['company_id'], [
'name' => 'idx_users_company',
])
->addIndex(['status', 'del_flg'], [
'name' => 'idx_users_status_del',
]);
// Add foreign keys
$table->addForeignKey('company_id', 'companys', 'id', [
'update' => 'CASCADE',
'delete' => 'RESTRICT',
]);
// Create table
$table->create();
}
}
3. Multi-Tenant Database Architecture
Pattern 1: Shared Database, Shared Schema:
-- All tenants in same tables with company_id
CREATE TABLE applications (
id INT PRIMARY KEY AUTO_INCREMENT,
company_id INT NOT NULL,
user_id INT NOT NULL,
status INT DEFAULT 1,
created DATETIME,
INDEX idx_company (company_id),
FOREIGN KEY (company_id) REFERENCES companys(id)
);
Pattern 2: Shared Database, Separate Schema (Multi-Tenant Pattern):
-- Account schema (shared)
CREATE DATABASE [app]_account_schema;
USE [app]_account_schema;
CREATE TABLE companys (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
database_name VARCHAR(100)
);
-- Company-specific schemas (per-tenant databases)
CREATE DATABASE [app_prefix]_company_9999;
USE [app_prefix]_company_9999;
CREATE TABLE applications (
id INT PRIMARY KEY AUTO_INCREMENT,
-- No company_id needed, database is company-specific
user_id INT NOT NULL,
status INT DEFAULT 1
);
4. Table Design Patterns
Standard Business Table:
CREATE TABLE orders (
-- Primary key
id INT PRIMARY KEY AUTO_INCREMENT,
-- Foreign keys
user_id INT NOT NULL,
company_id INT NOT NULL,
-- Business fields
order_number VARCHAR(50) UNIQUE NOT NULL,
total_amount DECIMAL(10,2) DEFAULT 0.00,
status INT DEFAULT 1,
order_date DATE NOT NULL,
-- Metadata
del_flg INT DEFAULT 0,
created DATETIME NOT NULL,
created_by INT,
modified DATETIME NOT NULL,
modified_by INT,
-- Indexes
INDEX idx_user (user_id),
INDEX idx_company (company_id),
INDEX idx_status_del (status, del_flg),
INDEX idx_order_date (order_date),
-- Foreign keys
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (company_id) REFERENCES companys(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Association Table (Many-to-Many):
CREATE TABLE users_roles (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
role_id INT NOT NULL,
created DATETIME NOT NULL,
-- Unique constraint for relationship
UNIQUE KEY unique_user_role (user_id, role_id),
-- Indexes for lookups
INDEX idx_user (user_id),
INDEX idx_role (role_id),
-- Foreign keys with CASCADE
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
) ENGINE=InnoDB;
Audit/History Table:
CREATE TABLE order_histories (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
action VARCHAR(50) NOT NULL, -- 'create', 'update', 'delete'
old_values JSON,
new_values JSON,
changed_by INT,
changed_at DATETIME NOT NULL,
INDEX idx_order (order_id),
INDEX idx_changed_at (changed_at),
FOREIGN KEY (order_id) REFERENCES orders(id)
) ENGINE=InnoDB;
5. Data Types and Constraints
Column Type Selection:
-- Strings
name VARCHAR(255) -- Variable length, max 255
description TEXT -- Large text, no limit
code CHAR(10) -- Fixed length
email VARCHAR(255) -- Email addresses
-- Numbers
id INT -- Primary keys
quantity INT -- Whole numbers
price DECIMAL(10,2) -- Money (10 digits, 2 decimal)
percentage FLOAT -- Floating point
big_number BIGINT -- Large integers
-- Dates
created DATETIME -- Date and time
birth_date DATE -- Date only
start_time TIME -- Time only
year YEAR -- Year only
-- Binary
image BLOB -- Binary data
file MEDIUMBLOB -- Larger binary
document LONGBLOB -- Very large binary
-- JSON (MySQL 5.7+)
settings JSON -- Structured data
metadata JSON -- Flexible schema
Constraints:
-- NOT NULL
email VARCHAR(255) NOT NULL
-- UNIQUE
email VARCHAR(255) UNIQUE
-- DEFAULT
status INT DEFAULT 1
created DATETIME DEFAULT CURRENT_TIMESTAMP
-- CHECK (MySQL 8.0+)
age INT CHECK (age >= 0 AND age <= 150)
status INT CHECK (status IN (1, 2, 3))
-- Foreign Key
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE RESTRICT -- Prevent deletion
ON UPDATE CASCADE -- Update child records
6. Index Design
Index Types and Usage:
-- Primary key (automatic unique index)
PRIMARY KEY (id)
-- Unique index
UNIQUE INDEX idx_email (email)
-- Composite index (order matters!)
INDEX idx_company_status (company_id, status)
-- Full-text index (for search)
FULLTEXT INDEX idx_description (description)
-- Spatial index (for geometry)
SPATIAL INDEX idx_location (location)
Index Strategy:
-- Frequent WHERE conditions
SELECT * FROM users WHERE company_id = ?;
-- Need: INDEX (company_id)
-- Sorting
SELECT * FROM orders ORDER BY created DESC;
-- Need: INDEX (created)
-- Joins
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id;
-- Need: INDEX on orders(user_id)
-- Covering index (all data in index)
SELECT id, email FROM users WHERE company_id = ?;
-- Need: INDEX (company_id, id, email)
7. Migration Management
Migration Naming Convention:
YYYYMMDDHHMMSS_ActionDescription.php
Examples:
20240101120000_CreateUsersTable.php
20240102130000_AddEmailToUsers.php
20240103140000_AlterUsersAddIndex.php
20240104150000_DropOldUsersTable.php
Migration Operations:
// Add column
$table->addColumn('new_field', 'string', [
'after' => 'existing_field',
'null' => true,
]);
// Modify column
$table->changeColumn('field_name', 'text', [
'null' => false,
]);
// Remove column
$table->removeColumn('old_field');
// Add index
$table->addIndex(['field1', 'field2'], [
'name' => 'idx_custom_name',
]);
// Remove index
$table->removeIndex(['field_name']);
// Add foreign key
$table->addForeignKey('user_id', 'users', 'id');
// Remove foreign key
$table->dropForeignKey('user_id');
8. Multi-Database Migration
Database-Specific Migrations:
# Directory structure (multi-tier pattern)
config/Migrations/
├── [ProjectDefault]/ # Project account database
├── [AppDefault]/ # Application account database
└── [AppClient]/ # Company-specific database (per-tenant)
Migration with Database Context:
// [AppClient] migration (company-specific)
class CreateApplications extends AbstractMigration
{
public function change(): void
{
$table = $this->table('applications');
// No company_id needed in company-specific DB
$table->addColumn('user_id', 'integer')
->addColumn('status', 'integer');
$table->create();
}
}
Database Patterns
1. Soft Delete Pattern
-- Add del_flg column
ALTER TABLE users ADD COLUMN del_flg INT DEFAULT 0;
-- Query active records
SELECT * FROM users WHERE del_flg = 0;
-- Soft delete
UPDATE users SET del_flg = 1, modified = NOW() WHERE id = ?;
2. Status Management Pattern
-- Status column with constants
CREATE TABLE applications (
id INT PRIMARY KEY,
status INT NOT NULL,
-- 1: applying, 2: applied, 3: approved, 4: rejected
CHECK (status IN (1, 2, 3, 4))
);
-- Query by status
SELECT * FROM applications
WHERE status = 1 -- Configure::read('Application.Status.applying')
3. Hierarchical Data Pattern
-- Self-referencing for tree structure
CREATE TABLE categories (
id INT PRIMARY KEY,
parent_id INT NULL,
name VARCHAR(255),
lft INT, -- For nested set model
rght INT,
FOREIGN KEY (parent_id) REFERENCES categories(id)
);
4. Audit Trail Pattern
-- Separate audit table
CREATE TABLE audit_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
model VARCHAR(100),
foreign_key INT,
action VARCHAR(20),
user_id INT,
change_data JSON,
created DATETIME,
INDEX idx_model_key (model, foreign_key)
);
Performance Optimization
1. Query Optimization
-- Use EXPLAIN to analyze
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- Optimize with proper index
CREATE INDEX idx_user_status ON orders(user_id, status);
-- Avoid SELECT *
SELECT id, name, email FROM users; -- Better
-- Use LIMIT for pagination
SELECT * FROM orders LIMIT 20 OFFSET 40;
2. Table Optimization
-- Analyze table statistics
ANALYZE TABLE orders;
-- Optimize table (rebuild)
OPTIMIZE TABLE orders;
-- Check table health
CHECK TABLE orders;
-- Repair if needed
REPAIR TABLE orders;
3. Partitioning Strategy
-- Range partitioning by date
CREATE TABLE orders (
id INT,
order_date DATE,
...
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025)
);
Output Examples
Example 1: User Management Schema
-- Users table
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role_id INT NOT NULL,
company_id INT NOT NULL,
status INT DEFAULT 1,
last_login DATETIME,
login_count INT DEFAULT 0,
del_flg INT DEFAULT 0,
created DATETIME NOT NULL,
modified DATETIME NOT NULL,
INDEX idx_email (email),
INDEX idx_company_status (company_id, status, del_flg),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (company_id) REFERENCES companys(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Example 2: Order System Schema
-- Orders master
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(50) UNIQUE NOT NULL,
user_id INT NOT NULL,
total DECIMAL(10,2) DEFAULT 0.00,
status INT DEFAULT 1,
created DATETIME NOT NULL,
INDEX idx_user (user_id),
INDEX idx_created (created DESC),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Order details
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
subtotal DECIMAL(10,2) NOT NULL,
INDEX idx_order (order_id),
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
Best Practices
- Use InnoDB: For transaction support and foreign keys
- UTF8MB4: For full Unicode support including emoji
- Consistent Naming: Use snake_case for tables and columns
- Add Indexes: For WHERE, JOIN, ORDER BY columns
- Avoid NULLs: When possible, use DEFAULT values
- Document Schema: Comment complex relationships
- Version Control: Track all migrations in Git
Remember: Database design is the foundation of application performance and data integrity.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon