Laravelのdistinctメソッドとは
LaravelのDistinctメソッドは、Eloquentクエリビルダーで提供される強力な機能の1つです。このメソッドを使用することで、データベースクエリの結果から重複したレコードを除外し、ユニークな結果セットを取得することができます。
distinctメソッドの基本的な仕組み
distinctメソッドは、SQLのDISTINCT句をラップした機能として実装されています。基本的な動作の仕組みは以下の通りです:
- クエリビルダーレベルでの処理
- クエリビルダーがSQLクエリを構築する際に、DISTINCT句を適切な位置に挿入
- 指定されたカラムのみを対象とした重複除外処理を実行
- クエリの実行計画を最適化
- データベースレベルでの処理
- 指定されたカラムの値を基準に一意性チェックを実行
- 重複データの排除処理を実行
- 結果セットの生成
基本的な実装例:
// 基本的な使用方法
$uniqueUsers = DB::table('users')
->distinct()
->get(['email']);
// 特定のカラムに対する使用
$uniqueCategories = DB::table('products')
->select('category_id')
->distinct()
->get();
// 複数カラムの組み合わせ
$uniqueCombinations = DB::table('orders')
->select('user_id', 'product_id')
->distinct()
->get();
SQLのDISTINCTとの違いを理解する
LaravelのdistinctメソッドとSQLのDISTINCT句には、いくつかの重要な違いがあります:
- 使用性の違い 機能 Laravel Distinct SQL DISTINCT メソッドチェーン 対応 非対応 動的な条件付け 簡単 複雑 クエリビルダー統合 ネイティブ 手動
- 機能的な特徴
- Laravelでの拡張機能
- リレーション内での使用が容易
- クエリスコープとの連携が可能
- モデルイベントとの連携が可能
- パフォーマンスの考慮点
- Eloquentのオーバーヘッド
- モデルのハイドレーション処理が追加
- メモリ使用量が若干増加
- 最適化機能
- インデックスの自動活用
- クエリキャッシュの利用
実装例での違い:
// Laravelでの実装
$uniqueEmails = User::distinct()
->select('email')
->where('active', true)
->get();
// 同等のSQL
SELECT DISTINCT email
FROM users
WHERE active = true;
LaravelのDistinctメソッドは、開発効率とコードの可読性を重視しつつ、SQLのDISTINCT句の機能を完全にカバーする形で実装されています。フレームワークの機能を活用することで、より保守性の高いコードを書くことができ、かつパフォーマンスも最適化された形で利用することが可能です。
distinctメソッドの基本的な使い方
シンプルなカラムでのdistinct処理
単一のカラムに対するdistinct処理は、最も基本的な使用方法です。以下のような場面で活用できます:
- メールアドレスの重複除外
// ユニークなメールアドレスの取得
$uniqueEmails = DB::table('users')
->select('email')
->distinct()
->get();
// 特定の条件下でのユニークな値取得
$activeUserEmails = User::query()
->select('email')
->where('status', 'active')
->distinct()
->get();
- カテゴリーの一覧取得
// ユニークなカテゴリー名の取得
$uniqueCategories = Product::query()
->select('category_name')
->distinct()
->orderBy('category_name')
->get();
複数カラムを組み合わせたdistinct処理
複数のカラムを組み合わせることで、より複雑な重複除外が可能になります:
- 基本的な複数カラムの組み合わせ
// 都道府県と市区町村の組み合わせを重複なしで取得
$uniqueLocations = Address::query()
->select('prefecture', 'city')
->distinct()
->orderBy('prefecture')
->orderBy('city')
->get();
- 集計関数との組み合わせ
// 部署ごとの固有の役職数を取得
$roleCountByDepartment = Employee::query()
->select('department')
->selectRaw('COUNT(DISTINCT role) as unique_roles')
->groupBy('department')
->get();
実装時の注意点:
- selectステートメントは必ず指定する
- 順序はwhereの後にdistinctを配置
- 必要なカラムのみを選択し、パフォーマンスを考慮
リレーション内でのdistinct活用法
Laravelのリレーション機能と組み合わせることで、より高度な重複除外が可能になります:
- hasOneリレーションでの使用
// ユーザーごとの最新の注文を重複なしで取得
$latestOrders = User::query()
->select('users.*')
->distinct()
->join('orders', 'users.id', '=', 'orders.user_id')
->orderBy('orders.created_at', 'desc')
->get();
- hasManyリレーションでの使用
// 商品を購入したことのあるユニークな顧客を取得
$uniqueBuyers = Customer::query()
->select('customers.*')
->distinct()
->join('orders', 'customers.id', '=', 'orders.customer_id')
->has('orders')
->get();
- belongsToManyでの活用
// 特定の権限を持つユニークな役割を取得
$uniqueRoles = Role::query()
->select('roles.*')
->distinct()
->join('role_permissions', 'roles.id', '=', 'role_permissions.role_id')
->where('role_permissions.permission_id', 1)
->get();
実装のベストプラクティス:
| 状況 | 推奨される実装方法 | 注意点 |
|---|---|---|
| 単一カラム | シンプルなselect + distinct | カラム名を明示的に指定 |
| 複数カラム | 必要なカラムのみを選択 | インデックスの活用を考慮 |
| リレーション | joinを使用 | N+1問題に注意 |
エラー防止のためのチェックリスト:
- カラム名が正しく指定されているか
- リレーション定義が適切か
- 必要なインデックスが設定されているか
- クエリのパフォーマンスが考慮されているか
これらの基本的な使用方法を理解することで、より複雑なケースにも対応できるようになります。
実践的なdistinctの活用シーン
重複データの効率的な除外方法
実際のプロジェクトでよく遭遇する重複データの除外シナリオと、その効率的な解決方法を紹介します:
- ログデータからのユニークアクセス抽出
// 日付別のユニークアクセスIPアドレス数を取得
$uniqueVisitors = AccessLog::query()
->select(DB::raw('DATE(created_at) as date'))
->selectRaw('COUNT(DISTINCT ip_address) as unique_visitors')
->groupBy('date')
->orderBy('date', 'desc')
->get();
- 重複エントリの検出と管理
// 重複する可能性のある顧客データを検出
$duplicateCustomers = Customer::query()
->select('email')
->selectRaw('COUNT(*) as count')
->groupBy('email')
->having('count', '>', 1)
->get();
// 重複を除外して最新のレコードのみを取得
$latestCustomers = Customer::query()
->select('email')
->selectRaw('MAX(id) as latest_id')
->groupBy('email')
->get()
->pluck('latest_id');
$uniqueCustomers = Customer::whereIn('id', $latestCustomers)->get();
集計処理での活用テクニック
集計処理でdistinctを効果的に活用する方法を紹介します:
- 期間別の集計
// 月別のユニークユーザー数を集計
$monthlyUniqueUsers = AccessLog::query()
->select(DB::raw('DATE_FORMAT(created_at, "%Y-%m") as month'))
->selectRaw('COUNT(DISTINCT user_id) as unique_users')
->groupBy('month')
->orderBy('month', 'desc')
->get();
// 週別の集計と前週比較
$weeklyStats = Order::query()
->select(DB::raw('YEARWEEK(created_at) as week'))
->selectRaw('COUNT(DISTINCT customer_id) as unique_customers')
->selectRaw('SUM(total_amount) as total_sales')
->groupBy('week')
->orderBy('week', 'desc')
->limit(10)
->get();
- カテゴリ別の集計
// 商品カテゴリ別のユニーク購入者数
$categoryStats = Order::query()
->join('products', 'orders.product_id', '=', 'products.id')
->select('products.category')
->selectRaw('COUNT(DISTINCT orders.customer_id) as unique_buyers')
->selectRaw('SUM(orders.quantity) as total_quantity')
->groupBy('products.category')
->get();
大規模データセットでの使用方法
大量のデータを扱う際の効率的なdistinct処理の実装方法:
- チャンク処理による大規模データの取り扱い
// 大規模なログデータからユニークユーザーを抽出
User::query()
->select('id', 'email')
->distinct()
->chunk(1000, function ($users) {
foreach ($users as $user) {
// 各ユーザーに対する処理
ProcessUserData::dispatch($user);
}
});
- ストリーミング処理との組み合わせ
// メモリ効率の良いストリーミング処理
$stream = User::query()
->select('email')
->distinct()
->lazyById(1000)
->filter(function ($user) {
return filter_var($user->email, FILTER_VALIDATE_EMAIL);
});
foreach ($stream as $user) {
// 各ユーザーのメールアドレスを処理
}
パフォーマンス最適化のポイント:
| 処理内容 | 最適化方法 | 期待される効果 |
|---|---|---|
| 大規模データの取得 | チャンク処理 | メモリ使用量の削減 |
| リアルタイム集計 | キャッシュの活用 | レスポンス時間の短縮 |
| 定期バッチ処理 | インデックスの活用 | 処理時間の短縮 |
実装時の注意事項:
- メモリ使用量の監視
- 適切なチャンクサイズの選定
- インデックスの効果的な活用
- キャッシュ戦略の検討
これらの実践的な実装例を参考に、プロジェクトの要件に応じた最適な実装を選択してください。
distinctメソッドのパフォーマンス最適化
インデックス設計のベストプラクティス
distinctクエリのパフォーマンスを最大限に引き出すためのインデックス設計について解説します:
- 単一カラムのインデックス設計
// マイグレーションでのインデックス設定
Schema::table('users', function (Blueprint $table) {
// distinctでよく使用されるカラムにインデックスを追加
$table->index('email');
$table->index(['status', 'email']); // 複合インデックス
});
// インデックスを活用したクエリ例
$uniqueActiveEmails = User::query()
->select('email')
->where('status', 'active')
->distinct()
->get();
- 複合インデックスの効果的な設計
// 複数カラムを使用する場合の最適なインデックス設計
Schema::table('orders', function (Blueprint $table) {
// 頻繁に使用される組み合わせに対するインデックス
$table->index(['customer_id', 'product_id', 'created_at']);
});
// 複合インデックスを活用したクエリ
$uniqueOrders = Order::query()
->select('customer_id', 'product_id')
->whereYear('created_at', '2024')
->distinct()
->get();
インデックス設計のチェックリスト:
- カラムの選択順序の最適化
- インデックスサイズの考慮
- 更新頻度とのバランス
- 使用頻度の分析
メモリ使用量を抑えるテクニック
大規模なデータセットを扱う際のメモリ最適化手法:
- カーソルの活用
// メモリ効率の良いカーソル処理
User::query()
->select('id', 'email')
->distinct()
->orderBy('id')
->cursor()
->each(function ($user) {
// 低メモリで大量データを処理
ProcessUserData::dispatch($user);
});
- チャンク処理の実装
// 最適なチャンクサイズでの処理
$chunkSize = 1000;
Order::query()
->select('customer_id')
->distinct()
->orderBy('customer_id')
->chunk($chunkSize, function ($orders) {
foreach ($orders as $order) {
// バッチ処理の実装
AnalyzeCustomerOrder::dispatch($order);
}
});
メモリ使用量最適化のベストプラクティス:
| 手法 | 用途 | メリット | デメリット |
|---|---|---|---|
| カーソル | 大規模データの逐次処理 | 低メモリ消費 | トランザクション必要 |
| チャンク | バッチ処理 | 制御が容易 | 若干のオーバーヘッド |
| LazyCollection | ストリーミング処理 | メモリ効率が良い | 一部機能制限あり |
N+1問題を回避する実装方法
distinctメソッドを使用する際のN+1問題対策:
- Eagerローディングの適切な使用
// N+1問題を回避するEagerローディング
$uniqueOrders = Order::query()
->select('customer_id')
->with(['customer' => function ($query) {
$query->select('id', 'name', 'email');
}])
->distinct()
->get();
// リレーション先のデータも含めた重複除外
$uniqueCustomers = Customer::query()
->select('customers.*')
->distinct()
->join('orders', 'customers.id', '=', 'orders.customer_id')
->with(['orders' => function ($query) {
$query->select('id', 'customer_id', 'total_amount');
}])
->get();
- サブクエリの最適化
// 効率的なサブクエリの使用
$latestOrders = Order::query()
->select('customer_id')
->distinct()
->whereIn('id', function ($query) {
$query->select(DB::raw('MAX(id)'))
->from('orders')
->groupBy('customer_id');
})
->with('customer:id,name,email')
->get();
パフォーマンスモニタリングのポイント:
- クエリログの分析
// クエリログの有効化(開発環境) DB::enableQueryLog(); // クエリの実行 $result = User::distinct()->get(['email']); // 実行されたクエリの確認 dd(DB::getQueryLog());
- 実行計画の確認
// 実行計画の取得
$explainResults = DB::select('EXPLAIN SELECT DISTINCT email FROM users WHERE status = ?', ['active']);
// 実行計画の分析
foreach ($explainResults as $result) {
// インデックスの使用状況などを確認
Log::info('Query execution plan:', (array) $result);
}
パフォーマンス最適化チェックリスト:
- 適切なインデックスの存在確認
- メモリ使用量のモニタリング
- N+1問題の有無チェック
- クエリ実行時間の測定
- 実行計画の定期的な確認
これらの最適化テクニックを適切に組み合わせることで、distinctメソッドを使用する際のパフォーマンスを大幅に改善することができます。
distinctメソッドの応用テクニック
サブクエリでのdistinct活用法
より複雑な要件に対応するためのサブクエリでのdistinct活用方法を解説します:
- 相関サブクエリでの使用
// 各カテゴリで最も売れている商品を取得
$topProducts = Category::query()
->select('categories.*')
->addSelect([
'best_selling_product' => Product::query()
->select('name')
->whereColumn('category_id', 'categories.id')
->orderByDesc(
Order::query()
->selectRaw('COUNT(DISTINCT customer_id)')
->whereColumn('product_id', 'products.id')
)
->limit(1)
])
->get();
- 派生テーブルでの活用
// 過去3ヶ月間の月別ユニークユーザー数の推移
$monthlyStats = DB::query()
->fromSub(function ($query) {
$query->from('access_logs')
->select(
DB::raw('DATE_FORMAT(created_at, "%Y-%m") as month'),
DB::raw('COUNT(DISTINCT user_id) as unique_users')
)
->where('created_at', '>=', now()->subMonths(3))
->groupBy('month');
}, 'monthly_stats')
->orderBy('month')
->get();
ソート処理と組み合わせた実装例
ソート処理を効果的に組み合わせる高度なテクニック:
- 複数条件でのソート
// 部署ごとの固有の役職数を給与レンジで並び替え
$departmentRoles = Department::query()
->select('departments.*')
->addSelect([
'unique_roles' => Employee::query()
->selectRaw('COUNT(DISTINCT role)')
->whereColumn('department_id', 'departments.id')
])
->addSelect([
'avg_salary' => Employee::query()
->selectRaw('AVG(salary)')
->whereColumn('department_id', 'departments.id')
])
->orderByDesc('unique_roles')
->orderByDesc('avg_salary')
->get();
- Window関数との組み合わせ
// 月別の上位顧客(ユニーク注文数基準)
$topCustomers = DB::query()
->select(
'month',
'customer_id',
'unique_orders',
DB::raw('ROW_NUMBER() OVER (PARTITION BY month ORDER BY unique_orders DESC) as rank')
)
->fromSub(function ($query) {
$query->from('orders')
->select(
DB::raw('DATE_FORMAT(created_at, "%Y-%m") as month'),
'customer_id',
DB::raw('COUNT(DISTINCT id) as unique_orders')
)
->groupBy('month', 'customer_id');
}, 'monthly_stats')
->having('rank', '<=', 5)
->orderBy('month')
->orderBy('rank')
->get();
キャッシュを活用した高速化手法
パフォーマンスを向上させるキャッシュ戦略:
- Results Caching
// キャッシュを活用した結果の保存
$uniqueCategories = Cache::remember('unique_categories', 3600, function () {
return Product::query()
->select('category')
->distinct()
->orderBy('category')
->get()
->pluck('category');
});
// タグ付きキャッシュの実装
$uniqueProducts = Cache::tags(['products', 'catalog'])->remember(
'unique_products',
now()->addHours(24),
function () {
return Product::query()
->select('id', 'name', 'category')
->distinct()
->where('status', 'active')
->get();
}
);
- Query Cache
// クエリキャッシュの活用
$expensiveQuery = Product::query()
->select('category', 'brand')
->distinct()
->whereHas('orders', function ($query) {
$query->whereYear('created_at', now()->year);
})
->whereHas('reviews', function ($query) {
$query->where('rating', '>=', 4);
})
->remember(60) // 1分間キャッシュ
->get();
高度な実装テクニック一覧:
| テクニック | 使用シーン | 主なメリット |
|---|---|---|
| サブクエリ | 複雑な集計 | 柔軟な条件指定 |
| Window関数 | ランキング作成 | 効率的な順位付け |
| キャッシュ | 頻繁なアクセス | レスポンス向上 |
| 相関サブクエリ | 関連データ取得 | データの一貫性 |
実装時の注意点:
- サブクエリの適切な使用
- インデックスの効果的な活用
- キャッシュ戦略の検討
- パフォーマンスの監視
これらの応用テクニックを活用することで、より複雑な要件にも対応可能な堅牢なアプリケーションを構築することができます。
よくあるエラーと解決方法
実行時エラーの原因と対処法
distinct使用時によく遭遇するエラーとその解決方法を解説します:
- カラム指定に関するエラー
// エラー例:Undefined column
$users = User::query()
->distinct()
->get(['non_existent_column']); // 存在しないカラムを指定
// 正しい実装
$users = User::query()
->distinct()
->get(['email', 'status']); // 実在するカラムを指定
// エラー回避のためのカラム存在確認
if (Schema::hasColumn('users', 'email')) {
$users = User::query()
->distinct()
->get(['email']);
}
- メモリ制限エラー
// メモリ不足を引き起こす実装
$allRecords = BigTable::distinct()->get(); // 危険:全データをメモリに読み込む
// 改善策:チャンク処理の実装
BigTable::query()
->select('id', 'category')
->distinct()
->chunk(1000, function ($records) {
foreach ($records as $record) {
// メモリ効率の良い処理
ProcessRecord::dispatch($record);
}
});
よくあるエラーと対処法一覧:
| エラー種類 | 主な原因 | 対処方法 |
|---|---|---|
| Column not found | カラム名の誤指定 | スキーマの確認 |
| Memory limit exceeded | データ量過多 | チャンク処理の導入 |
| Syntax error | SQLクエリの構文ミス | クエリビルダーの使用 |
| Timeout | 処理時間超過 | インデックス最適化 |
パフォーマンス低下時のデバッグ方法
パフォーマンス問題の特定と解決方法:
- クエリログの分析
// デバッグモードでのクエリログ取得
DB::enableQueryLog();
$result = Product::query()
->distinct()
->with('category')
->get();
// クエリの実行時間と詳細を確認
$queries = DB::getQueryLog();
foreach ($queries as $query) {
Log::debug('Query:', [
'sql' => $query['query'],
'bindings' => $query['bindings'],
'time' => $query['time']
]);
}
- パフォーマンスプロファイリング
// クエリ実行時間の計測
$startTime = microtime(true);
$result = Order::query()
->select('customer_id')
->distinct()
->whereYear('created_at', '2024')
->get();
$executionTime = microtime(true) - $startTime;
Log::info("Query execution time: {$executionTime} seconds");
SQLクエリ最適化のためのトラブルシューティング
実行計画の分析と最適化手法:
- EXPLAINの活用
// クエリの実行計画を取得
$query = User::query()
->select('role')
->distinct()
->where('status', 'active')
->toSql();
$bindings = User::query()
->select('role')
->distinct()
->where('status', 'active')
->getBindings();
$explainResults = DB::select("EXPLAIN {$query}", $bindings);
// 実行計画の分析
foreach ($explainResults as $result) {
if ($result->rows > 1000) {
Log::warning('Large table scan detected', [
'table' => $result->table,
'rows' => $result->rows
]);
}
}
- インデックス使用状況の確認
// インデックス使用状況の分析
$indexAnalysis = DB::select("
SELECT
INDEX_NAME,
COLUMN_NAME,
SEQ_IN_INDEX
FROM
information_schema.STATISTICS
WHERE
TABLE_SCHEMA = ? AND
TABLE_NAME = ?
", [
config('database.connections.mysql.database'),
'users'
]);
// インデックスの最適化提案
foreach ($indexAnalysis as $index) {
Log::info('Index analysis:', [
'index' => $index->INDEX_NAME,
'column' => $index->COLUMN_NAME,
'sequence' => $index->SEQ_IN_INDEX
]);
}
トラブルシューティングのチェックリスト:
- パフォーマンス問題の特定
- スロークエリログの確認
- メモリ使用量の監視
- 実行時間の計測
- 問題解決のアプローチ
- インデックスの見直し
- クエリの最適化
- キャッシュの導入検討
- チャンク処理の実装
- 予防的対策
- 定期的なパフォーマンスモニタリング
- インデックスメンテナンス
- キャッシュ戦略の見直し
- クエリログの分析
エラー発生時の対応フロー:
“`php
try {
// distinctを使用したクエリの実行
$result = DB::transaction(function () {
return Order::query()
->select(‘customer_id’)
->distinct()
->get();
});
} catch (QueryException $e) {
// エラーログの記録
Log::error(‘Query execution failed’, ‘error’ => $e->getMessage(), ‘sql’ => $e->getSql(), ‘bindings’ => $e->getBindings() );
// エラーハンドリング
if ($e->errorInfo[1] == 1205) { // Lock wait timeout
// デッドロック対策
retry(3, function () {
// クエリの再実行
}, 100);
}
} catch (\Exception $e) {
// その他のエラー処理
report($e);
throw $e;
}
実装例で学ぶdistinctの活用
ユーザー一覧での重複除外実装
実際のユーザー管理システムでよく必要となる重複除外の実装例を紹介します:
- 複数の会員ステータスを持つユーザーの一覧表示
// モデル定義
class User extends Model
{
public function memberships()
{
return $this->hasMany(Membership::class);
}
}
// リポジトリクラスの実装
class UserRepository
{
public function getUniqueActiveUsers()
{
return User::query()
->select('users.*')
->distinct()
->join('memberships', 'users.id', '=', 'memberships.user_id')
->where('memberships.status', 'active')
->with(['memberships' => function ($query) {
$query->where('status', 'active');
}])
->orderBy('users.created_at', 'desc')
->paginate(20);
}
// 重複アカウントの検出と統合
public function findDuplicateUsers()
{
return User::query()
->select('email')
->selectRaw('COUNT(*) as count')
->selectRaw('GROUP_CONCAT(id) as user_ids')
->groupBy('email')
->having('count', '>', 1)
->get()
->map(function ($result) {
return [
'email' => $result->email,
'count' => $result->count,
'ids' => explode(',', $result->user_ids)
];
});
}
}
- アクセス権限管理での活用
class PermissionService
{
public function getUsersWithUniquePermissions()
{
return User::query()
->select('users.*')
->distinct()
->join('role_user', 'users.id', '=', 'role_user.user_id')
->join('role_permission', 'role_user.role_id', '=', 'role_permission.role_id')
->with(['roles.permissions'])
->get()
->map(function ($user) {
return [
'id' => $user->id,
'name' => $user->name,
'unique_permissions' => $user->roles
->flatMap->permissions
->unique('id')
->values()
];
});
}
}
商品カテゴリでの活用事例
EC サイトでよく必要となる商品カテゴリ関連の実装例:
- 階層化されたカテゴリの一覧表示
class CategoryRepository
{
public function getUniqueParentCategories()
{
return Category::query()
->select('categories.*')
->distinct()
->whereNull('parent_id')
->with(['children' => function ($query) {
$query->select('id', 'parent_id', 'name')
->orderBy('name');
}])
->get();
}
// 商品が登録されているカテゴリのみを取得
public function getActiveCategories()
{
return Category::query()
->select('categories.*')
->distinct()
->join('products', 'categories.id', '=', 'products.category_id')
->where('products.status', 'active')
->withCount(['products' => function ($query) {
$query->where('status', 'active');
}])
->orderBy('products_count', 'desc')
->get();
}
}
- カテゴリ別の商品分析
class ProductAnalytics
{
public function getCategoryPerformance()
{
return DB::query()
->select([
'categories.name as category',
DB::raw('COUNT(DISTINCT products.id) as product_count'),
DB::raw('COUNT(DISTINCT orders.id) as order_count'),
DB::raw('SUM(order_items.quantity * order_items.price) as total_revenue')
])
->from('categories')
->join('products', 'categories.id', '=', 'products.category_id')
->leftJoin('order_items', 'products.id', '=', 'order_items.product_id')
->leftJoin('orders', 'order_items.order_id', '=', 'orders.id')
->groupBy('categories.id', 'categories.name')
->orderByDesc('total_revenue')
->get();
}
}
アクセスログ集計での実践例
アクセスログの分析や集計で活用できる実装例:
- 期間別のユニークアクセス分析
class AccessLogAnalytics
{
public function getUniqueVisitorStats($startDate, $endDate)
{
return AccessLog::query()
->select([
DB::raw('DATE(created_at) as date'),
DB::raw('COUNT(DISTINCT ip_address) as unique_visitors'),
DB::raw('COUNT(DISTINCT session_id) as unique_sessions'),
DB::raw('COUNT(DISTINCT user_id) as unique_users')
])
->whereBetween('created_at', [$startDate, $endDate])
->groupBy(DB::raw('DATE(created_at)'))
->orderBy('date')
->get()
->map(function ($stat) {
return [
'date' => $stat->date,
'unique_visitors' => $stat->unique_visitors,
'unique_sessions' => $stat->unique_sessions,
'unique_users' => $stat->unique_users,
'conversion_rate' => $stat->unique_users / $stat->unique_visitors * 100
];
});
}
// ページごとのユニークアクセス数を取得
public function getPageViewStats($period = '30days')
{
$startDate = now()->sub($period);
return AccessLog::query()
->select([
'page_url',
DB::raw('COUNT(*) as total_views'),
DB::raw('COUNT(DISTINCT ip_address) as unique_views'),
DB::raw('AVG(TIMESTAMPDIFF(SECOND, created_at, updated_at)) as avg_time_on_page')
])
->where('created_at', '>=', $startDate)
->groupBy('page_url')
->orderByDesc('unique_views')
->get()
->map(function ($stat) {
return [
'page_url' => $stat->page_url,
'total_views' => $stat->total_views,
'unique_views' => $stat->unique_views,
'avg_time_on_page' => round($stat->avg_time_on_page, 2),
'return_rate' => ($stat->total_views - $stat->unique_views) / $stat->unique_views * 100
];
});
}
}
実装のポイント:
- モデル設計
- 適切なリレーションの定義
- インデックスの効果的な設定
- スコープの活用
- クエリの最適化
- 必要なカラムのみの選択
- 適切なJOINの使用
- インデックスを考慮したWHERE句
- パフォーマンス対策
- チャンク処理の導入
- キャッシュの活用
- N+1問題の回避
これらの実装例は、実際のプロジェクトですぐに活用できる形で提供しています。要件に応じて適切にカスタマイズしてご利用ください。