Какие способы выборки данных из базы данных вы знаете?

Ответ

В разработке на PHP, особенно с фреймворками вроде Symfony/Laravel, выборка данных из БД осуществляется на разных уровнях абстракции. Вот основные подходы:

1. Нативный SQL (с использованием PDO): Прямое выполнение SQL-запросов. Дает максимальный контроль и производительность для сложных запросов.

$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE active = :active AND created_at > :date");
$stmt->execute(['active' => 1, 'date' => '2024-01-01']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC); // или fetch() для одной строки

2. Query Builder (Построитель запросов): Объектно-ориентированный API для построения SQL-запросов. Безопаснее и гибче, чем конкатенация строк.

// Пример с Doctrine DBAL (Symfony)
$queryBuilder = $connection->createQueryBuilder();
$users = $queryBuilder
    ->select('u.id', 'u.name', 'u.email')
    ->from('users', 'u')
    ->where('u.active = :active')
    ->andWhere('u.created_at > :date')
    ->setParameter('active', 1)
    ->setParameter('date', '2024-01-01')
    ->executeQuery()
    ->fetchAllAssociative();

3. ORM (Object-Relational Mapping, например, Doctrine ORM): Работа с данными как с объектами. Самый высокоуровневый подход.

  • Методы репозитория:
    $userRepository = $entityManager->getRepository(User::class);
    $user = $userRepository->find(123); // По первичному ключу
    $activeUsers = $userRepository->findBy(['active' => true]); // По простым критериям
  • DQL (Doctrine Query Language) или QueryBuilder Doctrine ORM: Для сложных запросов с объединениями.
    $query = $entityManager->createQuery(
        'SELECT u, p FROM AppEntityUser u JOIN u.posts p WHERE u.active = :active ORDER BY u.name'
    )->setParameter('active', 1);
    $users = $query->getResult(); // Возвращает массив объектов User с заполненной коллекцией posts

4. «Жадная» (EAGER) vs «Ленивая» (LAZY) загрузка:

  • Ленивая (по умолчанию в Doctrine): Связанные сущности (например, посты пользователя) загружаются только при первом обращении к ним. Может привести к проблеме N+1 запроса.
  • Жадная: Связанные сущности загружаются сразу в основном запросе (через JOIN). Задается в аннотации @ORMManyToOne(fetch="EAGER") или в DQL/QueryBuilder явным JOIN и выборкой.

Критерии выбора:

  • ORM (Doctrine) — для основной бизнес-логики, где удобна работа с объектами.
  • Query Builder (DBAL) — для сложных отчетов или административных страниц, где ORM может быть неэффективен.
  • Нативный SQL — для крайне специфичных или оптимизированных запросов, которые сложно выразить через ORM/Query Builder.

Ответ 18+ 🔞

Да ты посмотри, какая овердохуища вариантов нарисовалась! Прямо как в столовке меню: на первое, на второе и компот. Сейчас разжую, как есть, без соплей.

Ну, первое — это нативный SQL, через PDO. Старая, добрая, как дедовский топор. Максимальный контроль, скорость огонь, но и прострелить себе ногу проще простого, если неаккуратно. Всё руками пишешь, параметры сам биндишь. Для админских отчётов или какой-нибудь хитро-ёбанной аналитики — самое то. Только смотри, не накосячь с экранированием, а то будет тебе хиросима в базе данных.

$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE active = :active AND created_at > :date");
$stmt->execute(['active' => 1, 'date' => '2024-01-01']);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC); // или fetch() для одной строки

Второй вариант — Query Builder, он же построитель запросов. Это уже не голый SQL, но ещё и не полный ORM. Как бы промежуточная стадия, удобная такая. Типа собираешь запрос из кубиков, и он сам за тебя параметры подставляет. Удобно, когда запрос динамический формируется, с кучей if. Риск SQL-инъекции почти нулевой, если, конечно, не пытаться быть гением и не вклеивать куски строк напрямую. В Symfony это обычно Doctrine DBAL.

// Пример с Doctrine DBAL (Symfony)
$queryBuilder = $connection->createQueryBuilder();
$users = $queryBuilder
    ->select('u.id', 'u.name', 'u.email')
    ->from('users', 'u')
    ->where('u.active = :active')
    ->andWhere('u.created_at > :date')
    ->setParameter('active', 1)
    ->setParameter('date', '2024-01-01')
    ->executeQuery()
    ->fetchAllAssociative();

И наконец, третий, самый жирный — ORM, в частности Doctrine. Это уже высший пилотаж, работа с базой как с объектами. Вообще, красота, ёпта. Забываешь про SQL, думаешь классами и связями. Но, бля, это палка о двух концах. С одной стороны — удобство, с другой — можно такую дичь наворотить с производительностью, что мама не горюй.

Вот смотри, простые штуки через репозиторий делаются в одну строку:

$userRepository = $entityManager->getRepository(User::class);
$user = $userRepository->find(123); // По первичному ключу
$activeUsers = $userRepository->findBy(['active' => true]); // По простым критериям

А для сложных вещей есть DQL (типа SQL, но для объектов) или его Query Builder. Особенно, когда нужно с джойнами.

$query = $entityManager->createQuery(
    'SELECT u, p FROM AppEntityUser u JOIN u.posts p WHERE u.active = :active ORDER BY u.name'
)->setParameter('active', 1);
$users = $query->getResult(); // Возвращает массив объектов User с заполненной коллекцией posts

И вот тут главная засада подстерегает — ленивая загрузка. По умолчанию в Doctrine она включена. Это значит, что связанные посты (u.posts) из примера выше на самом деле не загрузятся в этом запросе. Они подтянутся отдельным запросом только тогда, когда ты к ним в коде первый раз обратишься. И если ты в цикле пройдёшься по 100 пользователям и для каждого выведешь его посты — у тебя будет 1 запрос за пользователей + 100 запросов за посты. Это и есть пресловутая проблема N+1, которая сожрёт твою производительность в ноль, ядрёна вошь!

Чтобы этого не было, нужно делать жадную загрузку — сразу тянуть всё нужное в одном запросе через JOIN. Либо в аннотации прописать fetch="EAGER", либо в DQL явно джойнить и селектить, как в примере выше.

Так какого хуя выбирать?

  • Doctrine ORM — твой daily driver для бизнес-логики. Работаешь с сущностями, всё красиво, объектно.
  • Query Builder (DBAL) — когда ORM начинает буксовать на сложных выборках, или пишешь какую-нибудь админку с фильтрами, где запрос собирается по кусочкам. Производительность лучше, чем у ORM, но не надо возиться с голыми строками SQL.
  • Нативный SQL через PDO — когда всё остальное не справляется. Запрос на три экрана с оконными функциями, сложнейшая аналитика. Используй точечно, как скальпель, а не как лом.

Выбирай с умом, а то потом сиди и гадай, почему твоя страница грузится как будто на дворе 2002-й год и модемное соединение.