Files
Mitgliederverwaltung/.plans/open/issue-200-n-plus-one-queries-member-list.md
T
shahondin1624 b29a268b1d Restructure .plans/ into done/ and open/ subdirectories
- Move completed plan files to .plans/done/
- Move 18 open plan files to .plans/open/
- Update .gitignore to exclude .verified_plans temp file
- Verified all 18 open plans still describe unimplemented issues
2026-04-28 20:30:55 +02:00

1.8 KiB
Raw Blame History

Issue #200: N+1 Query Problem in Member List

Problem

The MemberService::findAll() method (and similar methods) loads members, then makes 3 additional queries per member to fetch addresses, phones, and emails:

public function findAll(?int $limit = null, ?int $offset = null): array {
    $members = $this->memberMapper->findAll($limit, $offset);
    return array_map(function (Member $member) {
        $id = $member->getId();
        $addresses = $this->addressMapper->findByMemberId($id);  // +1 query
        $phones = $this->phoneMapper->findByMemberId($id);        // +1 query
        $emails = $this->emailMapper->findByMemberId($id);        // +1 query
        return $this->buildMemberResponse($member, $addresses, $phones, $emails);
    }, $members);
}

For a page of 20 members, this results in 1 + 20×3 = 61 queries.

Impact

  • Slow page loads, especially with pagination
  • Increased database load
  • Poor performance on larger member lists

Solution

Create a new mapper method that uses JOINs to fetch all data in a single query:

SELECT m.*, a.*, p.*, e.*
FROM mv_members m
LEFT JOIN mv_addresses a ON m.id = a.member_id
LEFT JOIN mv_phones p ON m.id = p.member_id
LEFT JOIN mv_emails e ON m.id = e.member_id
WHERE m.deleted_at IS NULL
ORDER BY m.nachname, m.vorname

Then parse the result set in PHP to group related sub-entities.

Tasks

  • Create MemberMapper::findAllWithRelations(?int $limit, ?int $offset): array that returns raw joined data
  • Create a DTO or use an associative array to represent joined results
  • Update MemberService::findAll() to use the new method
  • Apply same pattern to findFiltered(), findByFamily(), search(), fullTextSearch()
  • Benchmark before/after query counts

Labels

  • enhancement
  • backend
  • priority:high
  • performance