hyperf/docs/en/db/querybuilder.md
2022-10-31 22:43:05 +07:00

21 KiB

Query builder

Introduction

Hyperf's database query builder provides a convenient interface for creating and running database queries. It can be used to perform most database operations in an application and runs on all supported database systems.

Hyperf's query builder uses PDO parameter binding to protect your application from SQL injection attacks. So there is no need to sanitize strings passed as bindings.

Only some commonly used tutorials are provided here, and specific tutorials can be viewed on the Laravel official website. Laravel Query Builder

Get results

use Hyperf\DbConnection\Db;

$users = Db::select('SELECT * FROM user;');
$users = Db::table('user')->get();
$users = Db::table('user')->select('name', 'gender as user_gender')->get();

The Db::select() method returns an array, and the get method returns Hyperf\Utils\Collection. The element is stdClass, so the data of each element can be returned by the following code

<?php

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

Convert the result to array format

In some scenarios, you may want to use Array instead of stdClass object structure in the query result, and Eloquent removes the default FetchMode configured by configuration, then At this point, you can change the configuration by listening to the Hyperf\Database\Events\StatementPrepared event through the listener:

<?php
declare(strict_types=1);

namespace App\Listener;

use Hyperf\Database\Events\StatementPrepared;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use PDO;

/**
 * @Listener
 */
class FetchModeListener implements ListenerInterface
{
    public function listen(): array
    {
        return [
            StatementPrepared::class,
        ];
    }

    public function process(object $event)
    {
        if ($event instanceof StatementPrepared) {
            $event->statement->setFetchMode(PDO::FETCH_ASSOC);
        }
    }
}

Get the value of a column

If you want to get a collection containing a single column of values, you can use the pluck method. In the following example, we will get a collection of titles in the roles table:

<?php
use Hyperf\DbConnection\Db;

$names = Db::table('user')->pluck('name');

foreach ($names as $name) {
    echo $names;
}

You can also specify custom keys for fields in the returned collection:

<?php
use Hyperf\DbConnection\Db;

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

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

Chunked results

If you need to process thousands of database records, you might consider using the chunk method. This method takes a small chunk of the result set at a time and passes it to the closure function for processing. This method is very useful when Command is writing thousands of pieces of processing data. For example, we can cut the entire user table data into small pieces that process 100 records at a time:

<?php
use Hyperf\DbConnection\Db;

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

You can stop fetching chunked results by returning false in the closure:

use Hyperf\DbConnection\Db;

Db::table('user')->orderBy('id')->chunk(100, function ($users) {

    return false;
});

If you are updating database records while chunking the results, the chunked results may not be the same as expected. Therefore, when updating records in chunks, it is better to use the chunkById method. This method will automatically paginate the results based on the record's primary key:

use Hyperf\DbConnection\Db;

Db::table('user')->where('gender', 1)->chunkById(100, function ($users) {
    foreach ($users as $user) {
        Db::table('user')
            ->where('id', $user->id)
            ->update(['update_time' => time()]);
    }
});

Any changes to the primary or foreign keys may affect the block query while updating or deleting records inside the block's callback. This may result in records not being included in the chunked result.

Aggregate query

The framework also provides aggregate class methods such as count, max, min, avg, sum.

use Hyperf\DbConnection\Db;

$count = Db::table('user')->count();

Determine if the record exists

In addition to using the count method to determine whether the result of a query condition exists, you can also use the exists and doesntExist methods:

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

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

Inquire

Specify a Select statement

Of course you may not always want to get all the columns from the database table. Using the select method, you can customize a select query statement to query the specified fields:

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

The distinct method forces the query to return unique results:

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

If you already have a query builder instance and want to add a field to the existing query, you can use the addSelect method:

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

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

Original expression

Sometimes you need to use raw expressions in a query, for example to implement COUNT(0) AS count, which requires the use of the raw method.

use Hyperf\DbConnection\Db;

$res = Db::table('user')->select('gender', Db::raw('COUNT(0) AS `count`'))->groupBy('gender')->get();

Native method

The following methods can be used instead of Db::raw to insert raw expressions into various parts of the query.

The selectRaw method can be used in place of select(Db::raw(...)). The second parameter of this method is optional, and the value is an array of bound parameters:

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

The whereRaw and orWhereRaw methods inject native where into your query. The second parameter of these two methods is still optional, and the value is still an array of bound parameters:

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

The havingRaw and orHavingRaw methods can be used to set a raw string as the value of a having statement:

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

The orderByRaw method can be used to set a raw string as the value of the order by clause:

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

Join table

Inner Join Clause

Query builders can also write join methods. To perform basic "inner join", you can use the join method on the query builder instance. The first argument passed to the join method is the name of the table you want to join, while the other arguments use the field constraints that specify the join. You can also join multiple tables in a single query:

$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

If you want to use "left join" or "right join" instead of "inner join", use the leftJoin or rightJoin methods. These two methods are used in the same way as the join method:

$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 Statement

Use the crossJoin method to do a "cross join" with the name of the table you want to join. A cross join produces a Cartesian product between the first table and the joined tables:

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

Advanced Join Statement

You can specify more advanced join statements. For example passing a closure as the second parameter of the join method. This closure accepts a JoinClause object, specifying the constraints specified in the join statement:

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

If you want to use "where" style statements on the join, you can use the where and orWhere methods on the join. These methods compare columns to values instead of columns to columns:

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

Subjoin query

You can use the joinSub, leftJoinSubandrightJoinSub` methods to join a query as a subquery. Each of their methods takes three parameters: a subquery, a table alias, and a closure that defines the associated fields:

$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();

Combined query

The query builder also provides a shortcut for "joining" two queries. For example, you can create a query first, then use the union method to union it with the second query:

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

$users = Db::table('users')
    ->whereNull('last_name')
    ->union($first)
    ->get();

Where statement

Simple Where Statement

In constructing a where query instance, you can use the where method. The most basic way to call where is to pass three parameters: the first parameter is the column name, the second parameter is any operator supported by the database system, and the third parameter is the value to be compared for the column.

For example, here is a query to verify that the value of the gender field is equal to 1:

$users = Db::table('user')->where('gender', '=', 1)->get();

For convenience, if you are simply comparing the column value to a given value, you can pass the value directly as the second parameter of the where method:

$users = Db::table('user')->where('gender', 1)->get();

Of course, you can also use other operators to write where clauses:

$users = Db::table('users')->where('gender', '>=', 0)->get();

$users = Db::table('users')->where('gender', '<>', 1)->get();

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

You can also pass an array of conditions to the where function:

$users = Db::table('user')->where([
    ['status', '=', '1'],
    ['gender', '=', '1'],
])->get();

Or Statement

You can chain where constraints together or add or clauses to the query. The orWhere method accepts the same parameters as the where method:

$users = Db::table('user')
    ->where('gender', 1)
    ->orWhere('name', 'John')
    ->get();

Other Where Statements

whereBetween

The whereBetween method verifies that the field value is between two given values:

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

whereNotBetween

The whereNotBetween method verifies that the field value is outside the given two values:

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

whereIn / whereNotIn

The whereIn method validates that the value of the field must exist in the specified array:

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

The whereNotIn method verifies that the value of the field must not exist in the specified array:

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

Parameter grouping

Sometimes you need to create more advanced where clauses, such as "where exists" or nested parameter groupings. The query builder can also handle these. Below, let's see an example of grouping constraints in parentheses:

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

As you can see, a Closure is written to the where method to construct a query builder to constrain a grouping. The Closure receives a query instance that you can use to set constraints that should be included. The above example will generate the following SQL:

select * from users where name = 'John' and (votes > 100 or title = 'Admin')

You should call this grouping with orWhere to avoid accidental application of global effects.

Where Exists Statement

The whereExists method allows you to use the where exists SQL statement. The whereExists method accepts a Closure parameter, the whereExists method accepts a Closure parameter, the closure takes a query builder instance allowing you to define queries placed in the exists clause:

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

The above query will produce the following SQL statement:

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

JSON Where Statement

Hyperf also supports querying fields of type JSON (only on databases that support type JSON).

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

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

You can also use whereJsonContains to query JSON arrays:

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

You can use whereJsonLength to query the length of a JSON array:

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

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

Ordering, Grouping, Limit, & Offset

orderBy

The orderBy method allows you to order the result set by a given field. The first parameter of orderBy should be the field you want to sort, and the second parameter controls the direction of sorting, which can be asc or desc

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

latest / oldest

The latest and oldest methods allow you to easily sort by date. It uses the created_at column as the sort by default. Of course, you can also pass custom column names:

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

inRandomOrder

The inRandomOrder method is used to randomly order the results. For example, you can use this method to find a random user.

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

groupBy / having

The groupBy and having methods can group results. The use of the having method is very similar to the where method:

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

You can pass multiple arguments to the groupBy method:

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

For more advanced having syntax, see havingRaw method.

skip / take

To limit the number of results returned, or to skip a specified number of results, you can use the skip and take methods:

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

Or you can also use the limit and offset methods:

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

Conditional statements

Sometimes you may want to execute a query only if the clause applies if a certain condition is true. For example, you might only want to apply a where statement if a given value exists in the request. You can do this by using the when method:

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

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

The when method executes the given closure only if the first argument is true. If the first argument is false , then the closure will not be executed

You can pass another closure as the third parameter of the when method. The closure will be executed if the first argument is false. To illustrate how to use this feature, let's configure the default ordering of a query:

$sortBy = null;

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

Insert

The query builder also provides the insert method for inserting records into the database. The insert method accepts an array of field names and field values for insertion:

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

You can even pass an array to the insert method to insert multiple records into the table

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

Auto Increment ID

If the table has an auto-incrementing ID, use the insertGetId method to insert the record and return the ID value

$id = Db::table('users')->insertGetId(
    ['email' => 'john@example.com', 'votes' => 0]
);

Update

Of course, in addition to inserting records into the database, the query builder can also update existing records via the update method. The update method, like the insert method, accepts an array containing the fields and values to update. You can constrain the update query with the where clause:

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

Update or Insert

Sometimes you may wish to update an existing record in the database, or create a matching record if it does not exist. In this case, the updateOrInsert method can be used. The updateOrInsert method accepts two parameters: an array of conditions to find the record, and an array of key-value pairs containing the record to update.

The updateOrInsert method will first try to find a matching database record using the key and value pair of the first argument. If the record exists, use the value in the second parameter to update the record. If the record is not found, a new record is inserted, and the updated data is a collection of two arrays:

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

Update JSON fields

When updating a JSON field, you can use the -> syntax to access the corresponding value in the JSON object, which is only supported on MySQL 5.7+:

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

Auto increment and decrement

The query builder also provides convenience methods for incrementing or decrementing a given field. This method provides a more expressive and concise interface than manually writing update statements.

Both methods receive at least one parameter: the column that needs to be modified. The second parameter is optional and controls the amount by which the column is incremented or decremented:

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

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

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

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

You can also specify fields to update during the operation:

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

Delete

The query builder can also delete records from a table using the delete method. Before using delete, you can add a where clause to constrain the delete syntax:

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

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

If you need to empty the table, you can use the truncate method, which will delete all rows and reset the auto-incrementing ID to zero:

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

Pessimistic lock

The query builder also contains some functions that can help you implement pessimistic locking on the select syntax. To implement a "shared lock" in a query, you can use the sharedLock method. Shared locks prevent selected data columns from being tampered with until the transaction is committed

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

Alternatively, you can use the lockForUpdate method. Use the "update" lock to prevent rows from being modified or selected by other shared locks:

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