option('apply'); $force = (bool) $this->option('force'); $report = (bool) $this->option('report') || ! $apply; if ($report) { $this->printDistribution(); } $conflicts = $this->conflictArtworkIds(); $zeros = $this->zeroCategoryArtworkIds(); $eligible = $this->eligibleArtworkIds($force); $this->info('Zero-category artworks: '.$zeros->count()); $this->info('Single-category eligible for backfill: '.$eligible->count()); $this->info('Multi-category conflicts (not auto-resolved): '.$conflicts->count()); if ($conflicts->isNotEmpty()) { $this->warn('Multi-category artwork IDs (first 50): '.$conflicts->take(50)->implode(', ')); } if (! $apply) { $this->comment('Dry run only. Re-run with --apply to write primary_category_id for single-category artworks.'); return self::SUCCESS; } $updated = 0; foreach ($eligible->chunk(500) as $chunk) { $rows = DB::table('artwork_category') ->select('artwork_id', 'category_id') ->whereIn('artwork_id', $chunk->all()) ->get() ->groupBy('artwork_id'); foreach ($rows as $artworkId => $memberships) { if ($memberships->count() !== 1) { continue; } $query = Artwork::query()->where('id', (int) $artworkId); if (! $force) { $query->whereNull('primary_category_id'); } $updated += $query->update([ 'primary_category_id' => (int) $memberships->first()->category_id, ]); } } $this->info("Updated {$updated} artwork(s)."); return self::SUCCESS; } private function printDistribution(): void { $distribution = DB::query() ->fromSub(function ($query): void { $query->from('artwork_category') ->select('artwork_id', DB::raw('COUNT(*) as category_count')) ->groupBy('artwork_id'); }, 'x') ->select('category_count', DB::raw('COUNT(*) as artworks')) ->groupBy('category_count') ->orderBy('category_count') ->get(); $this->table(['category_count', 'artworks'], $distribution->map(fn ($row): array => [ (int) $row->category_count, (int) $row->artworks, ])->all()); } /** * @return Collection */ private function eligibleArtworkIds(bool $force) { $query = DB::table('artwork_category') ->select('artwork_id') ->groupBy('artwork_id') ->havingRaw('COUNT(*) = 1'); if (! $force) { $query->whereIn('artwork_id', Artwork::query()->whereNull('primary_category_id')->select('id')); } return $query->pluck('artwork_id')->map(fn ($id): int => (int) $id); } /** * @return Collection */ private function conflictArtworkIds() { return DB::table('artwork_category') ->select('artwork_id') ->groupBy('artwork_id') ->havingRaw('COUNT(*) > 1') ->orderByRaw('COUNT(*) DESC') ->pluck('artwork_id') ->map(fn ($id): int => (int) $id); } /** * @return Collection */ private function zeroCategoryArtworkIds() { return Artwork::query() ->whereDoesntHave('categories') ->pluck('id') ->map(fn ($id): int => (int) $id); } }