How to translate the SQL query to Laravel Eloquent query? - laravel

I'm trying to make a complex query using Laravel Eloquent. I know how to do it using raw SQL query, but I don't have any idea how to do it using Eloquent.
Here is my SQL query, and it works perfectly:
select *
from students
where exists(select *
from (select student_movements.id AS sm_id, student_movements.direction, student_movements.deleted_at
from student_movements
inner join student_student_movements
on student_movements.id = student_student_movements.student_movement_id
where students.id = student_student_movements.student_id
and student_movements.deleted_at is null
order by student_movements.id desc
limit 1) as last_sm
where last_sm.direction = 1 AND last_sm.date >= 5-5-2022
);
My models have many-to-many relation using student_student_movements table:
Student
public function studentMovements(): BelongsToMany
{
return $this->belongsToMany(
StudentMovement::class,
'student_student_movements',
);
}
StudentMovement
public function students(): BelongsToMany
{
return $this->belongsToMany(
Student::class,
'student_student_movements'
);
}
My goal is to get all Students, who have the last Movement where direction = 1 and the date of the last Movement >= $someDate.
So, my question is: how to translate the SQL query to Eloquent? I saw many similar questions, but they are not helping me.
Thanks for any advice.

Use the whereHas method, then fine tune the sub query inside the closure to your needs.

You can use the whereHas and orWhereHas methods to define
additional query constraints on your has queries.
There is an example like that in laravel documentation
use Illuminate\Database\Eloquent\Builder;
// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
check the documentation here

Related

Eloquent ORM query

I currently have an Eloquent ORM query.
$uploadedSC = SC::select('id')->with(['cont'=> function ($q) {
return $q->with('crs:id,title')->select('*');
}])->where('act', true)->get();
But I wanna add a ->where('act',true) query to my crs table. However, it gives an error. How can I write this query?
$uploadedSC = SC::select('id')->with(['cont'=> function ($q) {
return $q->with('crs:id,title')->where('act',true)->select('*');
}])->where('act', true)->get();
You could do something like this:
$uploadedSC = SC::select('id')
->with(['cont.crs:id,title' => function ($crs) {
$crs->where('act', true);
}])
->where('act', true)
->get();
Depending on the direction of the relationship you must select the foreign key of cont in crs or you will receive an error. This is the problem of giving bad names to your models and tables, other programmers will not understand at the first time what you are trying to do.

Laravel eloquent filter on hasMany relationship

I am trying to filter on the field which has many relationships.
This is how my query looks:
Order::with(['fullOrderItems' => function($query) use ($branch) {
$query
->where('branch', $branch->key);
}])
->orderBy('dateCreated')
->toSql();
The query looks like this once outputted:
select * from `Order` where `stage` <> ? order by `dateCreated` asc
And it seems like the query doesn't even consider the nested query for filtering the branch.
My relationship in Order class is defined like this:
/*
*
* #return HasMany|null
*/
public function fullOrderItemss() : ?HasMany
{
return $this->hasMany(OrderItems::class, 'order', 'key');
}
Do you see what am I doing wrong here? How can I get the full query (including nested condition)?
To filter the Entity order with a relation you need to use whereHas
Order::whereHas('fullOrderItems', function($query) use ($branch) {
$query
->where('branch', $branch->key);
})
->with(['fullOrderItems' => function($query) use ($branch) {
$query
->where('branch', $branch->key);
}])
->orderBy('dateCreated')
->get();
You can for example get the order filtered by branch id but get all the fullOrderItems (ingore the branch id) of those fullOrderItems like this
Order::whereHas('fullOrderItems', function($query) use ($branch) {
$query
->where('branch', $branch->key);
})
->with('fullOrderItems')
->orderBy('dateCreated')
->get();
This last example will make it simpler to understand the difference between the two filters.
For why the with condition doesnt show on the query:
it is used on a second unique query that fetchs the relation using the ids of orders retrieved in the first query. that way you get the orders, each with their respective fullOrderItems with only two queries.

Laravel with-count subquery without relation

My target is to get collection of books with count of matches book.names in another table without relation
I get collection like this
$books = Books::paginate(20);
What I need now is to get the count of matches like this
SELECT COUNT(*) FROM `posts` WHERE `body` LIKE '%book.name%'
How can I do this with one query and avoiding unnecessary queries for each model, like eager loading
You can do it with eager loading without loading all post. There is a method called withCount
Books.php
public function posts() {
return $this->hasMany(Post::class, 'post_id', 'id');
}
One way to find all post related to book is
$books = Books::withCount(['posts' => function ($query) use($searchTerm) {
$query->where('body', 'like', $searchTerm);
}])->get();
//How to retrieve
$book = $books->first()->posts_count.
You can find more information about withCount on laravel documentation website.
Approach 2: Without Eager Loading
$books = Books::select('*')->addSelect(DB::raw("SELECT COUNT(*) as post_count FROM `posts` WHERE `body` LIKE '%book.name%' ")->get(); //This will return collection with post_count.
Note: Not tested

How to use where by second table Laravel?

I have the following query:
$objects = Object::with("prototypes");
As you can see I do request to model Object and join it with prototypes.
The prototypes table has structure:
prototype_id
name
How to make where in above query like as:
$objects = Object::with("prototypes")->where("prototype_id", 3);
Object model:
public function prototypes()
{
return $this->belongsToMany('App\Prototype', 'object_prototype', 'object_id');
}
Prototype model:
public function objects(){
return $this->belongsToMany("App\Object", "object_prototype", "prototype_id", "object_id");
}
If the relationship is correctly set in your Eloquent models, you can use the WhereHas function to query relationship existence.
$objects = Object::with('prototypes')
->whereHas('prototypes', function ($query) {
$query->where('prototype_id', 3);
})
->get();
Just to add on #Jerodev answer.
If you need even more power, you may use the whereHas and
orWhereHas methods to put "where" conditions on your has queries.
These methods allow you to add customized constraints to a
relationship constraint, such as checking the content of a comment:
// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
You can read more about Eloquent Relationships
And in your case you can also construct a your query like.
Object::whereHas('prototypes', function ($query)){
$query->where('prototype_id', 3);
})->get();

laravel search many to many Relashionship

I am testing eloquent for the first time and I want to see if it suit my application.
I have Product table:
id, name
and model:
class Produit extends Eloquent {
public function eavs()
{
return $this->belongsToMany('Eav')
->withPivot('value_int', 'value_varchar', 'value_date');
}
}
and eav table:
id, name, code, field_type
and pivot table:
product_id, eav_id, value_int, value_varchar, value_date
class Eav extends Eloquent {
public function produitTypes()
{
return $this->belongsToMany(
'ProduitType'
->withPivot('cs_attributs_produits_types_required');
}
All this is working.
But I want to search in that relashionship:
e.g: all product that have eav_id=3 and value_int=3
I have tested this:
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
}))->get();
But I get all the product, and eav data only for these who have id=3 and value_int=3.
I want to get only the product that match this search...
Thank you
I know the question is very old. But added the answer that works in the latest versions of Laravel.
In Laravel 6.x+ versions you can use whereHas method.
So your query will look like this:
Produit::whereHas('eavs', function (Builder $query) {
// Query the pivot table
$query->where('eav_id', 3);
})->get()
My suggestion and something I like to follow is to start with what you know. In this case, we know the eav_id, so let's go from there.
$produits = Eav::find(3)->produits()->where('value_int', '3')->get();
Eager loading in this case isn't going to save you any performance because we are cutting down the 1+n query problem as described in the documentation because we are starting off with using find(). It's also going to be a lot easier to read and understand.
Using query builder for checking multiple eavs
$produits = DB::table('produits')
->join('eav_produit', 'eav_produit.produit_id', '=', 'produits.id')
->join('eavs', 'eavs.id', '=', 'eav_produit.eav_id')
->where(function($query)
{
$query->where('eav_produit.value_int','=','3');
$query->where('eavs.id', '=', '3');
})
->orWhere(function($query)
{
$query->where('eav_produit.value_int','=','1');
$query->where('eavs.id', '=', '1');
})
->select('produits.*')
->get();
Making it work with what you already have...
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
$query->orWhere('id', '1')->where('value_int', '1');
}))->get();
foreach($produits as $produit)
{
if(!produit->eavs)
continue;
// Do stuff
}
From http://four.laravel.com/docs/eloquent:
When accessing the records for a model, you may wish to limit your results based on the existence of a relationship. For example, you wish to pull all blog posts that have at least one comment. To do so, you may use the has method
$posts = Post::has('comments')->get();
Using the "has()" method should give you an array with only products that have EAV that match your criteria.
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
}))->has('eavs')->get();

Resources