I am trying to do a query in my Laravel app and I want to use a normal structure for my query. This class either does use Eloquent so I need to find something to do a query totally raw.
Might be something like Model::query($query);. Only that doesn't work.
You may try this:
// query can't be select * from table where
Model::select(DB::raw('query'))->get();
An Example:
Model::select(DB::raw('query'))
->whereNull('deleted_at')
->orderBy('id')
->get();
Also, you may use something like this (Using Query Builder):
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
Also, you may try something like this (Using Query Builder):
$users = DB::select('select * from users where id = ?', array(1));
$users = DB::select( DB::raw("select * from users where username = :username"), array('username' => Input::get("username")));
Check more about Raw-Expressions on Laravel website.
You can use hydrate() function to convert your array to the Eloquent models, which Laravel itself internally uses to convert the query results to the models. It's not mentioned in the docs as far as I know.
Below code is equviolent to $userModels = User::where('id', '>', $userId)->get();:
$userData = DB::select('SELECT * FROM users WHERE id > ?', [$userId]);
$userModels = User::hydrate($userData);
hydrate() function is defined in \Illuminate\Database\Eloquent\Builder as:
/**
* Create a collection of models from plain arrays.
*
* #param array $items
* #return \Illuminate\Database\Eloquent\Collection
*/
public function hydrate(array $items) {}
use DB::statement('your raw query here'). Hope this helps.
I don't think you can by default. I've extended Eloquent and added the following method.
/**
* Creates models from the raw results (it does not check the fillable attributes and so on)
* #param array $rawResult
* #return Collection
*/
public static function modelsFromRawResults($rawResult = [])
{
$objects = [];
foreach($rawResult as $result)
{
$object = new static();
$object->setRawAttributes((array)$result, true);
$objects[] = $object;
}
return new Collection($objects);
}
You can then do something like this:
class User extends Elegant { // Elegant is my extension of Eloquent
public static function getWithSuperFancyQuery()
{
$result = DB::raw('super fancy query here, make sure you have the correct columns');
return static::modelsFromRawResults($result);
}
}
Old question, already answered, I know.
However, nobody seems to mention the Expression class.
Granted, this might not fix your problem because your question leaves it ambiguous as to where in the SQL the Raw condition needs to be included (is it in the SELECT statement or in the WHERE statement?). However, this piece of information you might find useful regardless.
Include the following class in your Model file:
use Illuminate\Database\Query\Expression;
Then inside the Model class define a new variable
protected $select_cols = [
'id', 'name', 'foo', 'bar',
Expression ('(select count(1) from sub_table where sub_table.x = top_table.x) as my_raw_col'), 'blah'
]
And add a scope:
public function scopeMyFind ($builder, $id) {
return parent::find ($id, $this->select_cols);
}
Then from your controller or logic-file, you simply call:
$rec = MyModel::myFind(1);
dd ($rec->id, $rec->blah, $rec->my_raw_col);
Happy days.
(Works in Laravel framework 5.5)
use Eloquent Model related to the query you're working on.
and do something like this:
$contactus = ContactUS::select('*')
->whereRaw('id IN (SELECT min(id) FROM users GROUP BY email)')
->orderByDesc('created_at')
->get();
You could shorten your result handling by writing
$objects = new Collection(array_map(function($entry) {
return (new static())->setRawAttributes((array) $entry, true);
}, $result));
if you want to select info it is DB::select(Statement goes here) just remember that some queries wont work unless you go to Config/Database.php and set connections = mysql make sure 'strict' = false
Just know that it can cause some security concerns
if ever you might also need this.
orderByRaw() function for your order by.
Like
WodSection::orderBy('score_type')
->orderByRaw('FIELD(score_type,"score_type") DESC')
->get();
Related
Problem
I've spent the last few hours looking for a solution for this and can't seem to find anything that works. I'm trying to load all Routes that have at least one assigned Aircraft that is currently at the departure airport of the route, like this:
Route::has('availableAircraft');
The availableAircraft relationship on Route currently looks like this, with the issue stemming from not being able to find a way to inject the Route into the final where clause (see ROUTE_ID_HERE).
// Route.php
/**
* This returns all assigned aircraft that are not allocated to jobs and are at the departure airport of the route
*/
public function availableAircraft()
{
return $this->belongsToMany(
Aircraft::class, 'aircraft_route_assignments', 'route_id', 'aircraft_id')
->whereNull('current_job_id')
->where('current_airport_id', 'ROUTE_ID_HERE');
}
Raw SQL
I can perform this query using raw SQL, but I can't find a way to replicate this in Eloquent:
select
count(*) as aggregate
from
`routes`
where (
select
count(*)
from
`aircraft`
inner join `aircraft_route_assignments` on `aircraft`.`id` = `aircraft_route_assignments`.`aircraft_id`
where
`routes`.`id` = `aircraft_route_assignments`.`route_id`
and `current_job_id` is null
and `current_airport_id` = `routes`.`departure_airport_id`
) > 0
and `routes`.`deleted_at` is null
The crucial part here is the final and 'current_airport_id' = 'routes'.'departure_airport_id', which I can't seem to find a way to replicate in the query builder.
What I've Tried
I've tried to manually specify the field, like in the SQL query as so, but the actual SQL generated by this uses 'routes.departure_airport_id' as a literal string and so returns no results:
// Route.php
/**
* This returns all assigned aircraft that are not allocated to jobs and are at the departure airport of the route
*/
public function availableAircraft()
{
return $this->belongsToMany(
Aircraft::class, 'aircraft_route_assignments', 'route_id', 'aircraft_id')
->whereNull('current_job_id')
->where('current_airport_id', '`routes`.`departure_airport_id`');
}
Am I vastly over-thinking this?
Try this as your eloquent query:
Route::whereHas('availableAircraft', function (Builder $query) {
$query->whereNull('current_job_id')
->whereRaw('current_airport_id = `routes`.`departure_airport_id`');
})->get();
And change your model to this:
public function availableAircraft()
{
return $this->belongsToMany(
Aircraft::class, 'aircraft_route_assignments', 'route_id', 'aircraft_id');
}
I solved this by adapting Mahdi's answer to use whereRaw instead of where in the whereHas subquery. So the final Eloquent query looks like:
Route::whereHas('availableAircraft', function (Builder $query) {
$query->whereNull('current_job_id')
->whereRaw('current_airport_id = `routes`.`departure_airport_id`');
})->get()
I have created a one-to-many relationship. Here are the model classes.
class Photo extends Model
{
public function user(){
return $this->belongsTo('App\User');
}
}
class User extends Authenticatable
{
public function photos(){
return $this->hasMany('App\Photo');
}
}
Then I try to retrieve photos:
$photos = User::find(1)->photos->where('photo', 'ut.jpg')->first();
Here is a query log I got. I do not see the photo='ut.jpg'. So how laravel generate SQL?
select * from `photos` where `photos`.`user_id` = 1 and `photos`.`user_id` is not null
Try this
$photos = User::find(1)->photos()->where('photo', 'ut.jpg')->first();
must be use ->photos() instead of ->photos.
For see sql query use
$sql = User::find(1)->photos()->where('photo', 'ut.jpg')->toSql();
You queried all photos by using this:
$photos = User::find(1)->photos->where('photo', 'ut.jpg')->first();
By using User::find(1)->photos you receive a Laravel Collection. Those collections have a where method as well. So basically, you are running SQL to get all photos of User 1 and then you just filter that collection to only show you the item with photo ut.jpg.
Instead, you can use brackets to get the relationship, and then query that.
Your query then becomes
$photos = User::find(1)->photos()->where('photo', 'ut.jpg')->first();
Instead of naming it $photos you should name it $photo, as you are querying with first - which will result only in one object (or null).
Can you please try this:
$photo = 'ut.jpg';
$photos = User::find(1)->whereHas('photos', function ($query) use($photo){
return $query->where('photo', $photo);
})->first();
your query $photos = User::find(1)->photos->where('photo', 'ut.jpg')->first(); is incorrect, laravel didnt see the where condition if you do this
User::whereHas('photos', function($q) {
$q->where('photo', 'ut.jpg');
})->where('id',1)->first();
thats the correct query to get the user photo
You could:
Run A Select Query
$photos = DB::select('select * from photos where id = ?', [1]);
All this is well-documented in :
--https://laravel.com/docs/5.0/database
I have a function. It has method chaining that needs to be performed.
public function someFunction()
{
$query=$this->model;
$query->select($columns)
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
It was working fine but I need a slight modification in that function what I want is I want to pass a join in that function for method chaining.
public function someFunction($join_as_parameter)
{
$query=$this->model;
$query->select($columns)
//Join should be executed here as a parameter in method chaning .
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
So that final function execution will be like this
public function someFunction($join_as_parameter)
{
$query=$this->model;
$query->select($columns)
->join('table','sometable.id', '=', 'other_table')
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
Is there any way to do this? Any help would be appreciated. Thanks.
This way you can achieve what you need.
use DB;
use Closure;
use Illuminate\Database\Query\JoinClause;
public function someFunction(Closure $join_clauser)
{
//create Query Builder object
$query = DB::query();
//Add the `$join` object to the table joins for this query
$join_as_parameter = call_user_func($join_closure, $query);
$query->joins = array_merge((array) $query->joins, [$join_as_parameter]);
$query->select($columns)
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
//create Query Builder object
$query = DB::query();
And execute the functions as,
someFunction(function($query){
// return JoinClause object with joining conditions
return (new JoinClause($query, 'inner', 'table'))
->on('table.id', '=', 'othe_table.table_id');
});
Furthermore, you can modify this to pass array of joins to add multiple joins your the query.
To use this with eloquent models, replace
$query = DB::query();
with
$query = Model::query()->getQuery();
NOTE : ->getQuery() is used to retrieve the Query\Builder object since JoinClause expects it as the first param.
I'm developing an app on Laravel 5.5 and I'm facing an issue with a specific query scope. I have the following table structure (some fields omitted):
orders
---------
id
parent_id
status
The parent_id column references the id from the same table. I have this query scope to filter records that don't have any children:
public function scopeNoChildren(Builder $query): Builder
{
return $query->select('orders.*')
->leftJoin('orders AS children', function ($join) {
$join->on('orders.id', '=', 'children.parent_id')
->where('children.status', self::STATUS_COMPLETED);
})
->where('children.id', null);
}
This scope works fine when used alone. However, if I try to combine it with any another condition, it throws an SQL exception:
Order::where('status', Order::STATUS_COMPLETED)
->noChildren()
->get();
Leads to this:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'status' in where clause is ambiguous
I found two ways to avoid that error:
Solution #1: Prefix all other conditions with the table name
Doing something like this works:
Order::where('orders.status', Order::STATUS_COMPLETED)
->noChildren()
->get();
But I don't think this is a good approach since it's not clear the table name is required in case other dev or even myself try to use that scope again in the future. They'll probably end up figuring that out, but it doesn't seem a good practice.
Solution #2: Use a subquery
I can keep the ambiguous columns apart in a subquery. Still, in this case and as the table grows, the performance will degrade.
This is the strategy I'm using, though. Because it doesn't require any change to other scopes and conditions. At least not in the way I'm applying it right now.
public function scopeNoChildren(Builder $query): Builder
{
$subQueryChildren = self::select('id', 'parent_id')
->completed();
$sqlChildren = DB::raw(sprintf(
'(%s) AS children',
$subQueryChildren->toSql()
));
return $query->select('orders.*')
->leftJoin($sqlChildren, function ($join) use ($subQueryChildren) {
$join->on('orders.id', '=', 'children.parent_id')
->addBinding($subQueryChildren->getBindings());
})->where('children.id', null);
}
The perfect solution
I think that having the ability to use queries without prefixing with table name without relying on subqueries would be the perfect solution.
That's why I'm asking: Is there a way to have table name automatically added to Eloquent query methods?
I would use a relationship:
public function children()
{
return $this->hasMany(self::class, 'parent_id')
->where('status', self::STATUS_COMPLETED);
}
Order::where('status', Order::STATUS_COMPLETED)
->whereDoesntHave('children')
->get();
This executes the following query:
select *
from `orders`
where `status` = ?
and not exists
(select *
from `orders` as `laravel_reserved_0`
where `orders`.`id` = `laravel_reserved_0`.`parent_id`
and `status` = ?)
It uses a subquery, but it's short, simple and doesn't cause any ambiguity problems.
I don't think that performance will be a relevant issue unless you have millions of rows (I assume you don't). If the subquery performance will be a problem in the future, you can still go back to a JOIN solution. Until then, I would focus on code readability and flexibility.
A way to reuse the relationship (as pointed out by the OP):
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
Order::where('status', Order::STATUS_COMPLETED)
->whereDoesntHave('children', function ($query) {
$query->where('status', self::STATUS_COMPLETED);
})->get();
Or a way with two relationships:
public function completedChildren()
{
return $this->children()
->where('status', self::STATUS_COMPLETED);
}
Order::where('status', Order::STATUS_COMPLETED)
->whereDoesntHave('completedChildren')
->get();
In MySQL there are two good ways to find the leaf nodes (rows) in an adjacency list. One is the LEFT-JOIN-WHERE-NULL method (antijoin), which is what you did. The other is a NOT EXISTS subquery. Both methods should have a comparable performance (in theory they do exactly the same). However the subquery solution will not introduce new columns to the result.
return $query->select('orders.*')
->whereRaw("not exists (
select *
from orders as children
where children.parent_id = orders.id
and children.status = ?
)", [self::STATUS_COMPLETED]);
You must create a SomeDatabaseBuilder extending the original Illuminate\Database\Query\Builder, and a SomeEloquentBuilder extending the Illuminate\Database\Eloquent\Builder and, finally, a BaseModel extending Illuminate\Database\Eloquent\Model and overwrite these methods:
/**
* #return SomeDatabaseBuilder
*/
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new SomeDatabaseBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
/**
* #param \Illuminate\Database\Query\Builder $query
* #return SameEloquentBulder
*/
public function newEloquentBuilder($query)
{
return new SameEloquentBulder($query);
}
Then, on SomeDatabaseBuilder and SameEloquentBulder, change the methods to qualify columns by default (or make it optional).
I have a problem with ordering Eloquent collection result. This is my initial code:
class Major extends Eloquent {
protected $table = 'majors';
/**
* Gets all active majors
*
* #param $orderField Field by which the collection should be ordered
* #param $orderDirection Direction of ordering
*/
public static function getAllActive($orderField = 'order', $orderDirection = 'asc') {
$columns = array('id', 'name_de', 'name_en', 'name_'.Config::get('app.locale').' as name', 'active');
return self::all($columns)->where('active', 1);
}
}
The method to get the majors works fine. Now, I want to order the results by a specific field, so I changed the return to this:
return self::all($columns)->where('active', 1)->orderBy($orderField, $orderDirection);
The code throws the following error:
Call to undefined method Illuminate\Database\Eloquent\Collection::orderBy()
I need to keep the $columns variable as I need the name column alias. How do I order an Eloquent result properly?
all executes the query and returns a collection. You could use the select method:
self::whereActive(1)
->orderBy($orderField, $orderDirection)
->select($columns)
->get();
Ok, figured it out. The idea is to order the collection first and then select the columns:
return self::orderBy($orderField, $orderDirection)
->select($columns)
->where('active', 1)
->get();
Also, Vohuman's answer is totally valid as well.