Построение запросов

Содержание:


Введение

Конструктор запросов для базы данных Ларавел предоставляет удобный интерфейс для создания и выполнения запросов базы данных. Его можно использовать для большинства операций базы данных в вашем приложении. Кроме того, конструктор успешно работает со всеми поддерживаемыми системами баз данных.

Конструктор запросов Laravel привязку параметров PDO для защиты вашего приложения от SQL инъекций. Нет необходимости чистить строки, передаваемые в качестве привязки.

PDO не поддерживет связывание имён колонок. Поэтому, вам нужно никогда не позволять пользовательскому вводу диктовать имена колонок в привязке к вашим запросам, включая такие как "order by" и другие. Если есть необходимость позволить пользователям выбрать конкретные колонки для запросов, нужно убедитсья в том, что имя включено в список разрешённых.

Получение результатов

Получение всех столбцов из таблицы

Вы можете использовать метод table для фасада DB, чтобы начать запрос. Метод table возвращает экземпляр конструктора запросов для данной таблицы, позволяющий использовать другие выражения для запроса и наконец получить результаты используя метод get:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;

class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     *
     * @return Response
     */
    public function index()
    {
        $users = DB::table('users')->get();

        return view('user.index', ['users' => $users]);
    }
}

Метод get возвращает экземпляр Illuminate\Support\Collection содержащий результаты запроса, где каждый результат является экземпляром объекта PHP stdClass. Вы можете получить доступ к значению каждой колонки через свойство объекта:

foreach ($users as $user) {
    echo $user->name;
}

Получение строки / колонки из таблицы

Если вам просто необходимо получить строку из таблицы бд, вы можете использовать метод first. Этот метод вернёт одиночный объект stdClass:

$user = DB::table('users')->where('name', 'John')->first();

echo $user->name;

Если вам не нужны все значения строки, вы можете получить одиночное значение из записи используя метод value. Этот метод вернёт значение напрямую:

$email = DB::table('users')->where('name', 'John')->value('email');

Используйте метод find, чтобы получить один ряд по значению id:

$user = DB::table('users')->find(3);

Получение списка значений колонки

Если вы хотите получить Коллекцию содержащую значения одной колонки, вы можете использовать метод pluck. В этом примере, мы получим Коллекцию заголовков роли:

$titles = DB::table('roles')->pluck('title');

foreach ($titles as $title) {
    echo $title;
}

Вы можете также указать пользовательский ключ колонки для возвращаемой Коллекции:

$roles = DB::table('roles')->pluck('title', 'name');

foreach ($roles as $name => $title) {
    echo $title;
}

Деление результатов на части

Если вам необходимо работать с тысячами записей базы данных, убедитесь подумайте об использовании метод chunk. Этот метод получает необольшие куски результатов и передаёт каждый кусок в Closure для обработки. Этот метод очень полезен для написания команд Artisan, которые обрабатывают сотни записей. Например, давайте будем работать со всей таблицей users, по частям со 100 записями одновременно:

DB::table('users')->orderBy('id')->chunk(100, function ($users) {
    foreach ($users as $user) {
        //
    }
});

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

DB::table('users')->orderBy('id')->chunk(100, function ($users) {
    // Process the records...

    return false;
});

Если вы обновляете записи базы данных во время деления результатов на части, ваши чанки могут измениться непредсказуемо. Поэтому, при обновлении записей во время деления на части, лучше всего использовать метод chunkById. Этот метод будет проводить разбиение результатов основываясь на первичном ключе записи:

DB::table('users')->where('active', false)
    ->chunkById(100, function ($users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });
При обновлении или удалении записи внутри обратной связи, любые измения в первичном ключе или внешних ключах могут повлиять на запрос. Это может привести к тому, что записи не будут включены в чанк.

Агрегатные функции

Конструктор запросов также предоставляет набор агрегатных методов, таких как count, max, min, avg, and sum. Вы можете вызвать любой из этих методов после построения вашего запроса:

$users = DB::table('users')->count();

$price = DB::table('orders')->max('price');

Вы также сможете комбинировать эти методы с другими классами:

$price = DB::table('orders')
                ->where('finalized', 1)
                ->avg('price');

Определение существования записи

Вместо использования метода count для определения, что подходящая запись существует, вы можете использовать метод exists и doesntExist:

return DB::table('orders')->where('finalized', 1)->exists();

return DB::table('orders')->where('finalized', 1)->doesntExist();

Работа с Select

Указаний условий Select

Вы можете не всегда хотеть выбирать все колонки из таблицы базы данных. Используя метод select вы можете указать ваш выбор select для запроса:

$users = DB::table('users')->select('name', 'email as user_email')->get();

Метод distinct вынуждает запрос вернуть различные результаты:

$users = DB::table('users')->distinct()->get();

Если у вас уже есть экзмепляр конструктора запросов и вы хотите добавить колонку в существующее условие select, вы можете использовать метод addSelect:

$query = DB::table('users')->select('name');

$users = $query->addSelect('age')->get();

Выражения

Иногда вам может потребовать использовать чистые выражения для формирования запроса. Для создания выражения вы можете использовать метод DB::raw:

$users = DB::table('users')
                     ->select(DB::raw('count(*) as user_count, status'))
                     ->where('status', '<>', 1)
                     ->groupBy('status')
                     ->get();
Выражения будут внедрены в закпрос как строки. Поэтому вам необходимо быть очень аккуратными, чтобы не создавать уязвимости для проведения SQL инъекции.

Другие методы работы с чистыми выражениями

Вместо использования DB::raw, вы также можете использовать следующие методы для вставки чистых выражений в различные части вашего запроса.

selectRaw

Метод selectRaw можете быть использован вместо select(DB::raw(...)). Этот метод принимает необязательный массив связывания в качестве второго аргумента:

$orders = DB::table('orders')
                ->selectRaw('price * ? as price_with_tax', [1.0825])
                ->get();

whereRaw / orWhereRaw

Методы whereRaw и orWhereRaw могут быть использованы для внедрения случая с where в ваш запрос. Эти методы принимают необязательный массив связываний в качестве второго аргумента:

$orders = DB::table('orders')
                ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
                ->get();

havingRaw / orHavingRaw

Методы havingRaw и orHavingRaw могут быть использованы для установки чистой строки в качестве значения пункта having. Эти методы принимают необязательный массив связываний в качестве второго аргумента:

$orders = DB::table('orders')
                ->select('department', DB::raw('SUM(price) as total_sales'))
                ->groupBy('department')
                ->havingRaw('SUM(price) > ?', [2500])
                ->get();

orderByRaw

Метод orderByRaw может быть использован для установки выражения в качестве значения пункта order by:

$orders = DB::table('orders')
                ->orderByRaw('updated_at - created_at DESC')
                ->get();

Присоединения

Inner Join

Конструктор запросов может также использоваться для написания выражений с join. Для работы с типовым "inner join" можно использовать метод join на экземпляр конструктора. Первый аргумент, передаваемый в метод join, является именем таблицы, которую нжуно присоединить. Остальные аргументы нужны для указания условий присоединения колонок. Вы можете даже присоединить несколько таблиц в простом запросе:

$users = DB::table('users')
            ->join('contacts', 'users.id', '=', 'contacts.user_id')
            ->join('orders', 'users.id', '=', 'orders.user_id')
            ->select('users.*', 'contacts.phone', 'orders.price')
            ->get();

Left Join / Right

Если вам необходимо работать с "left join" и "right join" необходимо использовать методы leftJoin или rightJoin. Эти методы имеют схожий механзм работы с методом join:

$users = DB::table('users')
            ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
            ->get();

$users = DB::table('users')
            ->rightJoin('posts', 'users.id', '=', 'posts.user_id')
            ->get();

Cross Join

Используйте метод crossJoin тогда, когда необходим соответсвующая операция бд "cross join". Метод принимает название таблицы, которую необходимо присоединить. crossJoin генерирует декартово произведение между первой таблицей и добавленной таблицей:

$users = DB::table('sizes')
            ->crossJoin('colours')
            ->get();

Расширенное использование Join

Вы можете указать более сложное использованеи присоединений. Для начала передайте Closure в качестве второго аргумента в метод join. Closure получит объект JoinClause, который позволит вам указать диапазон ограничений для join:

DB::table('users')
        ->join('contacts', function ($join) {
            $join->on('users.id', '=', 'contacts.user_id')->orOn(...);
        })
        ->get();

Если вы хотите использовать "where" для ваших joins, вас могу заинтересовать методы where и orWhere для join. Вместо сравнения двух колонок, эти методы будут сравнивать столбец со значением:

DB::table('users')
        ->join('contacts', function ($join) {
            $join->on('users.id', '=', 'contacts.user_id')
                 ->where('contacts.user_id', '>', 5);
        })
        ->get();

Sub-Query Joins

Вы можете использовать методы joinSub, leftJoinSub, и rightJoinSub для присоединения запроса в подзапрос. Каждый из этих методов принимает три аргумента: подзапрос, псевдоним таблицы и замыкание, которое определяет связанные колонки:

$latestPosts = DB::table('posts')
                   ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
                   ->where('is_published', true)
                   ->groupBy('user_id');

$users = DB::table('users')
        ->joinSub($latestPosts, 'latest_posts', function ($join) {
            $join->on('users.id', '=', 'latest_posts.user_id');
        })->get();

Unions

Конструктор запросов также представляет возможность быстро объединить два запроса вместе. Например, вы можете создать начальный запрос и использовать метод union для объединения со вторым запросом:

$first = DB::table('users')
            ->whereNull('first_name');

$users = DB::table('users')
            ->whereNull('last_name')
            ->union($first)
            ->get();
Метод unionAll также доступен и имеет схожий механизм действия с методом union.

Условия Where

Простое условие Where

Вы можете использовать метод where на экзепляр конструктора запросов, чтобы добавить условие where в запрос. Типовой вызов where включает в состав 3 аргумента. Первый аргумент это имя колонки. Второй аргумент это оператор, который может быть любым поддерживаемым базой данных оператором. Наконец, третий аргумент это значение для сравнения со столбцом.

Например, представлен запрос, который проводит сравнивнение значения столбца и 100:

$users = DB::table('users')->where('votes', '=', 100)->get();

Для уверенности, можно просто не указывать оператор, а просто указать значение в качестве второго аргумента для метода where:

$users = DB::table('users')->where('votes', 100)->get();

Вы можете использовать набор операций при написании условий where:

$users = DB::table('users')
                ->where('votes', '>=', 100)
                ->get();

$users = DB::table('users')
                ->where('votes', '<>', 100)
                ->get();

$users = DB::table('users')
                ->where('name', 'like', 'T%')
                ->get();

Вы также можете передать массив условий в функцию where:

$users = DB::table('users')->where([
    ['status', '=', '1'],
    ['subscribed', '<>', '1'],
])->get();

Выражения с Or

Вы можете указывать ограничения цепочкой путём добавления or в запрос. Метод orWhere принимает такие же аргументы как и метод where:

$users = DB::table('users')
                    ->where('votes', '>', 100)
                    ->orWhere('name', 'John')
                    ->get();

Дополнительные условия Where

whereBetween / orWhereBetween

Метод whereBetween проводит проверку того, что значение расположено внутри указанного диапазона из двух чисел:

$users = DB::table('users')
           ->whereBetween('votes', [1, 100])
           ->get();

whereNotBetween / orWhereNotBetween

Метод whereNotBetween проводит проверку того, что значение не расположено внутри указанного диапазон из двух чисел:

$users = DB::table('users')
                    ->whereNotBetween('votes', [1, 100])
                    ->get();

whereIn / whereNotIn / orWhereIn / orWhereNotIn

Метод whereIn проверяет значения на то, что они равны одному из значений указанных в массиве:

$users = DB::table('users')
                    ->whereIn('id', [1, 2, 3])
                    ->get();

Метод whereNotIn проверяет значения на то, что они не равны значению указанным в массиве:

$users = DB::table('users')
                    ->whereNotIn('id', [1, 2, 3])
                    ->get();

whereNull / whereNotNull / orWhereNull / orWhereNotNull

Метод whereNull проводит проверку, что значение данной колонки NULL:

$users = DB::table('users')
                    ->whereNull('updated_at')
                    ->get();

Метод whereNotNull проводит проверку, что значение данной колонки не NULL:

$users = DB::table('users')
                    ->whereNotNull('updated_at')
                    ->get();

whereDate / whereMonth / whereDay / whereYear / whereTime

Метод whereDate может быть использован для сравнения значения колонки с определённой датой:

$users = DB::table('users')
                ->whereDate('created_at', '2016-12-31')
                ->get();

The whereMonth method may be used to compare a column's value against a specific month of a year:

$users = DB::table('users')
                ->whereMonth('created_at', '12')
                ->get();

Метод whereDay может быть использован для сравнения значения колонки с указанным днём месяца:

$users = DB::table('users')
                ->whereDay('created_at', '31')
                ->get();

Метод whereYear может быть использован для сравнения значения столбца с указанным годом:

$users = DB::table('users')
                ->whereYear('created_at', '2016')
                ->get();

Метод whereTime можно использовать для сравнения значения колонки с указанным временем:

$users = DB::table('users')
                ->whereTime('created_at', '=', '11:20:45')
                ->get();

whereColumn / orWhereColumn

Метод whereColumn можеть использован для проверки на равенство двух столбцов:

$users = DB::table('users')
                ->whereColumn('first_name', 'last_name')
                ->get();

Вы также можете передать оператор сравнения в метод:

$users = DB::table('users')
                ->whereColumn('updated_at', '>', 'created_at')
                ->get();

Метод whereColumn может также вкючать массив из множества условий. При добавлении условий используется оператор and:

$users = DB::table('users')
                ->whereColumn([
                    ['first_name', '=', 'last_name'],
                    ['updated_at', '>', 'created_at'],
                ])->get();

Группировка по параметрам

Иногда может потребоваться создать более сложное условие where, такие как "where exists" или сгруппированными параметрами. Конструтор запросов Laravel может справиться и с такими задачами. Для начала, давайте взглянем на пример группирования условий в скобках:

$users = DB::table('users')
           ->where('name', '=', 'John')
           ->where(function ($query) {
               $query->where('votes', '>', 100)
                     ->orWhere('title', '=', 'Admin');
           })
           ->get();

Как вы можете заметить, передача Closure в метод where укажет конструктору запросов о начале размещения сгруппированных параметров. Замыкание получит экземпляр конструтора запросов, который вы можете использовать, чтобы установить ограничения, располагающиеся внутри скобок. Указанный выше пример в конечном счёте произведёт следующий SQL:

select * from users where name = 'John' and (votes > 100 or title = 'Admin')
Вы всегда должны сгуппировать вызовы orWhere, чтобы избежать непредсказуемого поведения.

Условие Where Exists

Метод whereExists позволяет вам писать случай SQL where exists. Метод whereExists принимает Замыкание в качестве аргумента, которое получит экземпляр конструктора запроса. Это позволяет определить запрос, который расположен внутри "exists":

$users = DB::table('users')
           ->whereExists(function ($query) {
               $query->select(DB::raw(1))
                     ->from('orders')
                     ->whereRaw('orders.user_id = users.id');
           })
           ->get();

Для примера выше получим следующий SQL:

select * from users
where exists (
    select 1 from orders where orders.user_id = users.id
)

Условие JSON Where

Ларавел также поддерживает проведение запросов для столбцов типа JSON для баз данных, которые поддерживают такой тип. На текущий момент включены includes MySQL 5.7, PostgreSQL, SQL Server 2016, и SQLite 3.9.0 (с JSON1 расширением). Нужно использовать оператор -> для запросов JSON столбца:

$users = DB::table('users')
                ->where('options->language', 'en')
                ->get();

$users = DB::table('users')
                ->where('preferences->dining->meal', 'salad')
                ->get();

Вы можете использовать whereJsonContains для проведения запросов в массивы JSON (не поддерживается SQLite):

$users = DB::table('users')
                ->whereJsonContains('options->languages', 'en')
                ->get();

MySQL и PostgreSQL поддерживают whereJsonContains с несколькими значениями:

$users = DB::table('users')
                ->whereJsonContains('options->languages', ['en', 'de'])
                ->get();

Вы можете использовать whereJsonLength, для запросов массивох JSON по их длине:

$users = DB::table('users')
                ->whereJsonLength('options->languages', 0)
                ->get();

$users = DB::table('users')
                ->whereJsonLength('options->languages', '>', 1)
                ->get();

Упорядочивание, группировка, лимиты и смещение

orderBy

Метод orderBy позволяет вам отсортировать результаты запроса по выбранному столбцу. Первый аргумент для метод orderBy является именем столбца, в то время как второй аргумент отвечает за направлени сортировки и может быть asc или desc:

$users = DB::table('users')
                ->orderBy('name', 'desc')
                ->get();

latest / oldest

Метод latest и oldest позволяют сортировать результаты по дате. По умолчанию, результаты сортируются по столбцу created_at. Или, вы можете передать имя колонки, которую вы хотите отсортировать:

$user = DB::table('users')
                ->latest()
                ->first();

inRandomOrder

Метод inRandomOrder может быть использован для сортировки результатов запроса в случайном порядке. Например, вы можете использовать этот для получения случайного пользователя:

$randomUser = DB::table('users')
                ->inRandomOrder()
                ->first();

groupBy / having

Методы groupBy и having могут использоваться для группировки результатов запросов. Механизм работы метода having схож с методом where:

$users = DB::table('users')
                ->groupBy('account_id')
                ->having('account_id', '>', 100)
                ->get();

Вы можете передавать несколько аргументов в метод groupBy для группировки нескольких столбцов:

$users = DB::table('users')
                ->groupBy('first_name', 'status')
                ->having('account_id', '>', 100)
                ->get();

Для расширения возможностей having, рекомендуется рассмотреть метод havingRaw.

skip / take

Чтобы ограничить число результатов возвращаемых из запроса, или пропустить указанное число результатов в запросе, вы можете использовать методы skip и take:

$users = DB::table('users')->skip(10)->take(5)->get();

Как альтернатива, вы можете использовать методы limit и offset:

$users = DB::table('users')
                ->offset(10)
                ->limit(5)
                ->get();

Условный пункт

Иногда вы можете захотеть разрешить выполнение запроса только в том случае, если что-то истино. Выражение where будет запущено только при условии, что данное значение вввода присутсвует во входящем запросе. Вы можете решить такую задачу используя метод when:

$role = $request->input('role');

$users = DB::table('users')
                ->when($role, function ($query, $role) {
                    return $query->where('role_id', $role);
                })
                ->get();

Метод when будет выполнять данное замыкание при условии, что первый параметр true. Если первый параметр false, замыкание выполняться не будет.

Вы также можете передать другое замыкание в качестве третьего параметра в метод when. Это выражение будет выполняться в случае если первый параметр false. Для иллюстрации того, как это может быть использовано, мы будем использовать это для настройки типового запроса сортировки:

$sortBy = null;

$users = DB::table('users')
                ->when($sortBy, function ($query, $sortBy) {
                    return $query->orderBy($sortBy);
                }, function ($query) {
                    return $query->orderBy('name');
                })
                ->get();

Вставка

Конструктор запросов также предоставляет метод insert для вставки записей в таблицу базы данных. Метод insert принимает массив названий столбцов и значений:

DB::table('users')->insert(
    ['email' => 'kayla@example.com', 'votes' => 0]
);

Вы также можете вставить несколько записей в таблицу с одиночным вызовом insert путём передачи двумерного массива. Каждый массив представляет строку, которую необходимо вставить в таблицу:

DB::table('users')->insert([
    ['email' => 'picard@example.com', 'votes' => 0],
    ['email' => 'janeway@example.com', 'votes' => 0]
]);

Метод insertOrIgnore будет игнорировать ошибки о повторах записей во время вставки в базу данных:

DB::table('users')->insertOrIgnore([
    ['id' => 1, 'sisko@example.com'],
    ['id' => 2, 'email' => 'archer@example.com']
]);

Автоматическое увеличение идентификатора

Если таблица имеет идентификатор с отметкой "автоинкремент", используйте метод insertGetId для вставки записи и получения идентификатора:

$id = DB::table('users')->insertGetId(
    ['email' => 'john@example.com', 'votes' => 0]
);
При исопльзовании PostgreSQL метод insertGetId предполагает, что колонка с автоинкрементом будет называться code>id. Если вам нужен идентификатор из другой сущности, вы можете передать название колонки в качестве второго параметра в метод insertGetId.

Обновление

Дополнительно к вставке записей в базу данных, конструктор запросов может также обновлять существующие записи, используя метод update. Метод update, как и метод insert принимает массив пар с названием колонки и значением. Вы можете построить запрос update используя условия where:

$affected = DB::table('users')
              ->where('id', 1)
              ->update(['votes' => 1]);

Обновить или вставить

Иногда вы можете захотеть обновить существующую запись в базе даннных или создать запись, если такой ещё нет. В этом случае можно использовать метод updateOrInsert. Метод updateOrInsert принимает два аргумент: массив условий, по которым проводится поиск записи, и массив пар столбец / значение для обновления соотвествующих столбцов.

Метод updateOrInsert в первую очередь будет искать соответвующую запись в бд используя колонку пар и значений первого аргумента. Если такая запись существует, то запись будет обновлена значениями из второго аргумента. Если запись не удалось найти, новая запись будет вставлена с соединёнными аргументами из двух аргументов:

DB::table('users')
    ->updateOrInsert(
        ['email' =>'john@example.com', 'name' => 'John'],
        ['votes' => '2']
    );

Обновление JSON колонок

При обновлении JSON столбца, вы должны использовать синтаксис с -> для доступа к ключе в объекте JSON. Эта операция поддерживается для MySQL 5.7+ и PostgreSQL 9.5+:

$affected = DB::table('users')
              ->where('id', 1)
              ->update(['options->enabled' => true]);

Инкремент и декремент

Конструктор запросов также предоставляет удобные методы для автоматического увеличения или уменьшения значения данной колонки. Эти методы отличаются простотой и лаконичностью по сравнению с написанием традиционного метода update.

Оба эти метода принимают как минимум один аргумент: название столбца для изменения. Необязательный второй аргумент указаывает число, на которое необходимо увеличить или уменьшить иходное значение:

DB::table('users')->increment('votes');

DB::table('users')->increment('votes', 5);

DB::table('users')->decrement('votes');

DB::table('users')->decrement('votes', 5);

Вы можете также указать дополнительные колонки для обновления во время операции:

DB::table('users')->increment('votes', 1, ['name' => 'John']);

Удаления

Конструктор запросов может также использоваться для удаления записей из таблицы через метод delete. Вы можете указать точные условия удаления путём добавления условий where перед вызовом самого метода delete:

DB::table('users')->delete();

DB::table('users')->where('votes', '>', 100)->delete();

Если вы хотите удалить все записи таблицы полностью, а значение автоинкремента установить как 0, вы можете использовать метод truncate:

DB::table('users')->truncate();

Пессимистическая блокировка

Конструктор запросов также включает несколько функций, которые могут помочь вам наложить "пессимистическая блокировку" для вашего выражения select. Для выполнения операции с совместной блокировкой () "shared lock"), вы можете использовать метод sharedLock для запроса. Такая блокировка предотвращает выделенные строки от внесения изменений до потверждения транзакции:

DB::table('users')->where('votes', '>', 100)->sharedLock()->get();

Как альтернатива, вы можете использовать метод lockForUpdate. Данная блокировка предотвращает внесение изменение или выделения другой совместной блокировки:

DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get();

Отладка

Вы можете использовать методы dd или dump во время построения запроса, чтобы показать привязки запроса и SQL. Метод dd выведет информацию отладки и после остановит выполнение запроса. Метод dump покажет информацию отладки, но позволит запросу продолжить своё выполнение:

DB::table('users')->where('votes', '>', 100)->dd();

DB::table('users')->where('votes', '>', 100)->dump();