Skip to main content

Query Builder

The Voltis Query Builder offers you a convenient, expressive interface to interacting with your databases. It provides you with most of the functionality that nearly all queries require, making it easy and rapid to write SQL statements in your application. It also provides you with piece of mind as all data inputs are bound to the query using PHP PDO prepared statements, so you are protected against SQL injection attacks.

When using the Query Builder, Voltis will handle getting the correct type of connection for you automatically, read and write connections are retrieved from the connection pool for you as well, you don’t need to worry about database connections and their pools.

caution

Even though the Query Builder will automatically handle data binding for you with PHP PDO Prepared statements, protecting you from SQL injection attacks, column names are not protected by this, so it is down to you as the developer to use a whitelist to manage which columns are used in database queries. Never allow the user input to select columns, always use a whitelist to validate against.

Accessing the Query Builder

Wherever you use the query builder, you can access it through the database Facade by including Voltis\Database\Facade\DB in your namespaces that could be inside a controller, Middleware or your own class or service.

For example:

$users = DB::table("user")->get();

Gets everything from the table user and stores the result as an array in $users.

Selects

$result = DB::table("user")->select()->get();

Gets the whole user table and stores the result in $result, calling select without any arguments means you wish to * everything.

You can include * to indicate you want all columns:

$result = DB::table("user")->select("*")->get();

Selecting with columns

$result = DB::table("user")->select('username')->get();

You may pass multiple columns in like so:

$result = DB::table("user")->select('name', 'age', 'email')->get();

And you may pass in an array of columns:

$result = DB::table("user")->select(['name', 'age', 'email'])->get();

Select distinct

$result = DB::table("user")->select('age')->distinct()->get();

Select single row

$row = DB::table('user')->where('name', '=', 'Tim Jones')->first();

Returns only the first matching row from the database.

Select single column

$email = DB::table('user')->where('name', '=', 'Tim Jones')->value('email');

Returns only the value from the matched row, in this case the email directly is returned.

Select by ID

$id = DB::table('user')->findById(216);

Finds the row using the primary key and returns the user row from the database.

Select and return columns

$usernames = DB::table('test_table')->list('username');

Up-to-date Data

If you are using a multi-server database setup and have multiple read and write connections, you can force your query to use a write connection:

$results = DB::table('user')->select('name')->important()->get();

Aggregates

You may use count, min, max and sum and avg methods after you have constructed your query.

Count

$users = DB::table("user")->select('COUNT(username) as users')->get();

or

$users = DB::table('user')->where('age', '<', 30)->count('name');

Min

$min = DB::table('user')->min('age');

Max

$max = DB::table('user')->max('age');

Avg

$avg = DB::table('user')->avg('age');

Sum

$sum = DB::table('user')->sum('votes');

Chunking Results

Sometimes you may need to process thousands of database records and selecting all of them at once would be cumbersome and put strain on your applications performance, Voltis offers you a range of different chunking methods to handle things situations.

Each chunking method takes a closure which gets fed a small amount of records each time until all of the results are processed, reducing the amount of overhead of retrieving large amounts of rows at once.

Chunk

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

foreach($users as $user)
{
echo $user['name'];
}

});

By calling chunk you can see above that in the example 100 results each time are processed until all the users have been fed into the closure.

Remember you are free to constrain your chunk with a where for example:

DB::table('user')->where('age', '<', 20)->orderBy('id')->chunk(100, function($users) {...});
note

You may return false and stop anymore chunks from being processed

Defer and Chunk

The normal chunk function on its own is blocking and the request is not sent back until the chunk has finished, so in order to process your chunk and return back to the rest of your application you can defer a chunk.

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

foreach($users as $user)
{
echo $user['name'];
}

});

Chunk By ID

When you chunk results and update at the same time it could affect the results as the data is being changed at the same time, so Voltis provides you with chunkById instead. This method chunks by using the primary key to order and process rows.

DB::table('user')->where('age', '>', 55)->chunkById(100, function($users) {

foreach($users as $user)
{
echo $user['name'];
}

}, $column = null);

If you change or update data inside the callback for chunking, don’t alter any primary keys or foreign keys or you may experience unexpected results.

info

By default chunkById uses id as the primary key column name but as a third argument you can pass in the column name if your primary key name is different

Defer and Chunk By ID

The chunkById by default is blocking but you can use deferAndChunkById to defer the chunk and continue on with your request.

DB::table('user')->where('age', '>', 55)->deferAndChunkById(100, function($users) {

foreach($users as $user)
{
echo $user['name'];
}

});

Joins

Inner Joins

You can use the query builder to build up join clauses, to perform a basic inner join you can just use the join method.

DB::table('user')
->join('payments', 'user.id', '=', 'payment.id')
->join('invoice', 'invoice.id', '=', 'payment.id')
->select('user.name')
->get();

Above shows you how you can join multiple tables together, the first argument is the table you wish to join and then your constraints for each join.

Remember your join statements should come before any selects.

Left & Right Joins

You can use the query builder to also perform left or right joins by using either leftJoin or rightJoin methods.

DB::table('user')
->leftJoin('comments', 'user.id', '=', 'comments.userId')
->get();

DB::table('user')
->rightJoin('comments', 'user.id', '=', 'comments.userId')
->get();

Cross Joins

If you need to build a cross join you can use the crossJoin method from the query builder. A cross join is the number of rows in the first table multiplied by the number of rows in the second table; a Cartesian product between the tables.

DB::table('foods')
->crossJoin('company')
->get();

Wheres

Basic Where Clause

You can use where clauses to help filter down your result set from the database, a basic where clause requires three arguments. The first being the name of the column, second the operator and the value as the third.

Here is an example of a basic where query that selects all users who are over the age of 30:

$users = DB::table("user")->select('name', 'age')
->where('age', '>', 30)
->get();

Or another example where the active column is equal to 1:

$users = DB::table("user")->select('name', 'age')
->where('active', '=', 1)
->get();

Other supported operators are based on the database you are using but the most comment are:

  • >=: Greater than or equal
  • <=: Less than or equal
  • <>: Not equal (In some versions of SQL this operator may be written as !=)
  • LIKE: Search for a pattern

An example of a where like:

$users = DB::table("user")
->select('name', 'age')
->where('name', 'like', '%Luke%')
->get();

Adding an array of wheres

To make adding multiple where rules to your query you can pass an array like so:

$results = DB::table("user")
->select('name', 'age')
->where([
['age', '>=', 18],
['votes', '<', 100],
])
->get();

But you can also just keep adding where statements:

$results = DB::table("user")
->select('name', 'age')
->where('age', '>', 18)
->where('votes', '<', 100)
->get();

Chaining or adding wheres with arrays will use 'AND' to add one after another.

Or Where

The basic where method will always use 'AND' to add them to the query but you might want to use an 'OR' where, you can do this with the orWhere method:

$results = DB::table("user")->select('name', 'age')
->where('age', '<=', 30)
->orWhere('age', '=', 65)
->get();

You may also use an array to pass chain multiple together:

$results = DB::table("user")->select('name', 'age')
->where('age', '<=', 30)
->orWhere([
['age', '<>', 55],
['age', '=', 80],
])
->get();

Grouped Where

You may need to group a where condition with parentheses such as two or wheres and you can do that by using a closure:

$results = DB::table("user")
->select('name')
->where('name', '=', 'Luke')
->orWhere(function($query) {
$query->where('points', '>', 100)
->where('age', '=', '22');
})
->get();

The above would produce SELECT name FROM user WHERE name = 'Luke' OR (points > 100 AND age = 22)".

Where Between

To check that a column's value is between two values, you can use a where between:

$result = DB::table("user")->select('name', 'age')
->whereBetween('age', [12, 30])
->get();

$result = DB::table("user")->select('name', 'age')
->orWhereBetween('age', [12, 30])
->get();

Where Not Between

Check that a column's value is not between two certain values:

$results = DB::table("user")->select('name', 'age')
->whereNotBetween('age', [12, 30])
->get();

$results = DB::table("user")->select('name', 'age')
->orWhereNotBetween('age', [55, 80])
->get();

Where In

To check that a column's value is within the contained list:

$results = DB::table("user")->select('name', 'age')
->whereIn('age', [22, 44])
->get();

$results = DB::table("user")->select('name', 'age')
->orWhereIn('age', [22, 44])
->get();

Where Not In

Check if a column's value is not within a list of values:

$results = DB::table("user")->select('name', 'age')
->whereNotIn('age', [22, 44])
->get();

$results = DB::table("user")->select('name', 'age')
->orWhereNotIn('age', [22, 44])
->get();

Where Null

Check that a column is null:

$results = DB::table("user")->select('name', 'age')
->whereNull('first_name')
->get();

$results = DB::table("user")->select('name', 'age')
->orWhereNull('first_name')
->get();

Where Not Null

Check that a column is not null:

$results = DB::table("user")->select('name', 'age')
->whereNotNull('age')
->get();

$results = DB::table("user")->select('name', 'age')
->orWhereNotNull('age')
->get();

Where Date

Used to compared a column against a date:

$results = DB::table("user")->select('name', 'age')
->whereDate('created_at', '=', '2020-06-01')
->orWhereDate('created_at', '=', '2019-05-01')
->get();

Where Year

Used to compared a column against a specific year:

$results = DB::table("user")->select('name', 'age')
->whereYear('created_at', '=', '2019')
->orWhereYear('created_at', '=', '2018')
->get();

Where Month

Used to compared a column against a specific month:

$results = DB::table("user")->select('name', 'age')
->whereMonth('created_at', '=', '05')
->orWhereMonth('created_at', '=', '06')
->get();

Where Day

Used to compared a column against a specific day:

$results = DB::table("user")->select('name', 'age')
->whereDay('created_at', '=', '9')
->orWhereDay('created_at', '<', '05')
->get();

$results = DB::table("user")->select('name', 'age')
->whereDayOfYear('created_at', '>', '03')
->orWhereDayOfYear('created_at', '>', '03')
->get();

Where Week

Used to compared a column against a specific week number:

$results = DB::table("user")->select('name', 'age')
->whereWeekOfYear('created_at', '>', '03')
->orWhereWeekOfYear('created_at', '>', '03')
->get();

Where Time

Used to compared a column against a specific time:

$results = DB::table("user")->select('name', 'age')
->whereTime('created_at', '>', '16:49')
->orWhereTime('created_at', '>', '17:55')
->get();

Where Column

Used to compare two column's against each other:

$results = DB::table("user")->select('name')
->whereColumn('name', '=', 'username')
->orWhereColumn('name', '=', 'email')
->get();

You can change the operator to what you need.

caution

Where column does not prepare and escape column names so be careful not to pass user supplied data, use a white list instead

Advanced Where Grouping

When writing SQL sometimes you may need to group or nest clauses with parenthesis, you can do that with the Voltis Query Builder by passing in a Closure as the first argument to a where clause:

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

Which would produce the following SQL:

SELECT * FROM users WHERE name = 'John' AND (votes > 100 OR title = 'Admin')

Where Exists

A where exists is used to test for the existence of any record in a subquery, if the subquery exists record exists it will return true. You can build these types of queries in Voltis:

$results = DB::table("user")
->select('name', 'age')
->where('age', '>', 18)
->whereExists(function($query) {
$query->select('name')
->from('user')
->where('votes.vote_id', '=', 'user.id');
})
->get();

$results = DB::table("user")
->select('name', 'age')
->where('age', '>', 18)
->orWhereExists(function($query) {
$query->select('name')
->from('user')
->where('votes.vote_id', '=', 'user.id');
})
->get();

The SQL produced would be:

SELECT name, age FROM user WHERE age > 18 AND EXISTS (SELECT id FROM votes WHERE vote_id = user.id)

Where Not Exists

$results = DB::table("user")
->select('name', 'age')
->where('age', '>', 10)
->whereNotExists(function($query) {
$query->select('name')
->from('user')
->where('age', '>', 20);
})
->get();

$results = DB::table("user")
->select('name', 'age')
->where('age', '>', 10)
->orWhereNotExists(function($query) {
$query->select('name')
->from('user')
->where('age', '>', 20);
})
->get();

Where Subquery Clauses

To perform advanced where clauses with subqueries in them that allow you to compare a value against the result of a subquery, you may call the where function with a closure like so:

$results = DB::table('user')
->select('name')
->where(function($query) {
$query->select('allowed')
->from('permissions')
->whereColumn('user_id', '=', 'users.id')
->limit(1);
}, 'Tim Jones')
->get();

Which would perform the query:

SELECT name FROM user WHERE (SELECT allowed FROM permissions WHERE user_id = users.id LIMIT 1)

Where JSON

For databases that support JSON column types Voltis allows you to use these inside a where clause.

Currently supported databases for use of JSON columns and the query builder: MySQL 5.7+

To access JSON objects you can use the -> operator:

$results = DB::table('json_table')
->select('json_data->person->name')
->whereJson('json_data->comments->votes', '>', 40)
->orWhereJson('json_data->person->age', '<', 50)
->get();

Ordering

OrderBy

The orderBy method is used to sort the result-set in ascending or descending order, by default it always uses ascending.

$results = DB::table("user")->select('age')
->where('age', '>', 15)
->orderBy('age')
->get();

The first argument is the column you want to sort by and the second is either asc or desc.

You can also pass multiple arguments to the orderBy method:

$results = DB::table("user")->select('name', 'age')
->where('age', '>', 15)
->orderBy([['age', 'DESC'], ['name', 'DESC']])
->get();

Latest & Oldest

You can use the latest and oldest methods to quickly order your results by date, they both expect you to provide which column you want to order by.

Latest

$results = DB::table("user")->select('name', 'age')
->latest('created_at')
->get();

Oldest

$results = DB::table("user")->select('name', 'age')
->oldest('created_at')
->get();

Order By Random

If you need to order your results in a random order you can use orderByRandom which will order your results in a random order and you can pass in a seed if you wish.

$results = DB::table("user")->select('name', 'age')
->orderByRandom($seed = '')
->get();

Group By & Having

You can group query results by using a groupBy clause, the having method is similar to a basic where, for example:

$results = DB::table("user")
->groupBy('id')
->having('id', '>', 50)
->get();

The having clause exists because where cannot be used with aggregate functions, having is like a normal where but is applied after the results are grouped.

You can pass multiple arguments to the groupBy method, you also have access to a number of other having methods, here is an example of all the other methods you can use:

$results = DB::table("user")
->select('name')
->groupBy('id', 'age')
->having('age', '>', 16)
->orHaving('age', '<', 16)
->havingBetween('age', [10, 33])
->orHavingBetween('age', [10, 33])
->havingNotBetween('age', [10, 33])
->orHavingNotBetween('age', [10, 33])
->get();

Limit & Offset

If you want to limit the result set or to skip a number of results you can use limit and offset:

$results = DB::table("user")
->select('name')
->limit(100)
->offset(20)
->get();

However, you can use take to limit results and skip to offset a certain number of results as well:

$results = DB::table("user")
->select('name')
->skip(10)
->take(5)
->get();

Inserting

Even though you have the raw methods to insert data into your database, the query builder also provides you with an insert method as well. The insert method expects an array on data to insert into the database:

$affected = DB::table('user')->insert(['name' => 'Joe', 'age' => 23, 'score' => 89]);
$affected = DB::table('user')->insert([
['name' => 'Luke', 'age' => 23, 'score' => 99],
['name' => 'Dan', 'age' => 11, 'score' => 34],
['name' => 'James', 'age' => 11, 'score' => 67]
];

Auto-Incrementing ID

You can receive the auto-incrementing ID using the insertAndGetId method:

$id = DB::table('user')->insertAndGetId(['name' => 'Luke', 'age' => 23, 'score' => 55]);

Defer & Insert

If you have a load of data to insert into the database and don’t want to block your request from finishing you can use the deferAndInsert method which will allow you to defer the insert process and continue on with your request right away and not block the request.

$results = DB::table('user')->deferAndInsert([
['name' => 'Luke', 'age' => 23, 'score' => 44],
['name' => 'Dan', 'age' => 23, 'score' => 33],
['name' => 'Kai', 'age' => 23, 'score' => 22],
['name' => 'Conor', 'age' => 21, 'score' => 35],
['name' => 'Earp', 'age' => 22, 'score' => 56],
]);

Updating

You can also use the query builder to update existing records, the update method expects an array of columns and a value to update it with, similar to how the insert method works:

$affected = $results = DB::table('user')
->where('id', '=', 1)
->update(['bio' => 'My bio was updated!']);

And you can add on where clauses to constraint your update query.

Update or Insert

You may come across a situation where you want to update a record but if it does not exist then insert it, Voltis provides you with updateOrInsert to achieve this. This method 1st expects a condition to be passed in as a way to see if the record exists first, then the 2nd argument is the key value pairs to update the row with.

$updateOrInsert = $results = DB::table('user')
->updateOrInsert(
['name' => 'Luke', 'age' => 23],
['bio' => 'Luke's bio!']
, true);

The third argument is by default set to false and is used to return back the insert ID or not if the record does not exist and is inserted instead, set this to true and the insert ID is returned if an insert is performed.

If an insert is performed, both the condition and values array are merged together and that completes the full insert, otherwise it’s just the second array that is used for the update statement.

Updating JSON

You can also update JSON columns in your database, as like in a where clause you use the -> operator, you do the same with the update statement, for example:

$affected = $results = DB::table('json_table')
->where('id', '=', 1)
->update(['json_data->person->name' => 'Lucas', 'json_data->person->age' => 45]);

$affected = $results = DB::table('json_table')
->where('id', '=', 1)
->update(['json_data->person->votes' => 3]);

This is supported for MySQL 5.7 and up.

Increment & Decrement

To make it more convenient to update columns where you only need to increment or decrement values, the query builder provides you with both methods. This is a shorthand for updating columns quickly and in an expressive manner.

$affected = $results = DB::table('user')
->where('id', '=', 1)
->increment('age');

$affected = $results = DB::table('user')
->where('id', '=', 1)
->decrement('votes', 5);

Each method expects the column you want to update and then the update amount, by default it is set to 1.

Deletes

You can use the delete method to delete records from the database and include a where onto your statement if you want to constrain the query:

$affected = DB::table('user')->where('age', '>', 50)->delete();

The delete method can be passed an ID if you want to quickly delete a row with a column called id:

$affected = DB::table('user')->delete(44);

When using the delete method, if you want to delete all rows in a table, you must confirm with *:

$affected = DB::table('table2')->delete('*');

Lastly, you can use truncate to remove all the rows and reset the auto-incrementing ID back to zero:

$affected = DB::table('table3')->truncate();

Pessimistic Locking

When selecting data from your database, sometimes you will want that data to not change, when reading data you can use lockForShare to set a shared lock on your statement and transaction. Doing so will allow other connections to read the same row but not permit them to update the same row until your transaction completes.

The other type of lock is lockForUpdate where it sets a lock which blocks other transactions from reading the same row and other transaction will have to wait until the lock is released.

$results = DB::table("user")->select('name', 'age')
->where('age', '<', 2)
->lockForShare()
->get();

$results = DB::table("user")->select('name', 'age')
->where('age', '<', 2)
->lockForUpdate()
->get();

Debugging

For query debugging while using the query builder you have dump and dd. The first method allows you to dump both the query and data bindings while the second does the same but does not execute the SQL query and still continues on with your request.

$results = DB::table("user")->dump();
$results = DB::table("user")->dd();