Performance of whereHas laravel - laravel

I have read many articles warning about whereHas(it use subquery in where exists) performance when the data is big(Here: 18415, 5328, ...). In my case below will I need to replace it? And how?.
Table products
+----+---------------------+
| id | created_at |
+----+---------------------+
| 1 | 2020-10-10 10:10:10 |
| 2 | 2020-10-10 10:10:10 |
+----+---------------------+
Table product_translations(indexes: map_id, language_code, slug)
+----+--------+---------------+-----------+-----------+
| id | map_id | language_code | name | slug |
+----+--------+---------------+-----------+-----------+
| 1 | 1 | en | name en 1 | name-en-1 |
| 2 | 1 | es | name es 1 | name-es-1 |
| 3 | 2 | en | name en 2 | name-en-2 |
| 4 | 2 | es | name es 2 | name-es-2 |
+----+--------+---------------+-----------+-----------+
Product.php
function translation(){
return $this->hasOne('App\Models\ProductTranslation', 'map_id', 'id');
}
ProductController.php
function view($slug){
$currentItem = Product::with(['translation' => function($q) use($slug){
$q->where(['slug' => $slug, 'language_code' => \App::getLocale()]);
}])->whereHas('translation', function($q) use ($slug){
$q->where(['slug' => $slug, 'language_code' => \App::getLocale()]);
})
if ($currentItem == null) {
abort(404);
}
return view('product.view', compact('currentItem'));
}
I'm using laravel 8x.

You can replace whereHas with whereIn.
$currentItem = Product::with(['translation' => function($q) use($slug){
$q->where(['slug' => $slug, 'language_code' => \App::getLocale()]);
}])->whereIn(
'id',
ProductTranslation::select('map_id')
->where(['slug' => $slug, 'language_code' => \App::getLocale()])
);
Select the foreign key from a subselect and uses it with where in.

Related

Laravel query builder: group relations and count total amount

I'm using Laravel 9 and want to create chart for user-role dependency and I need count amount of users with specific role
This is my tables (for example)
// users
| id | name |
| --- | --- |
| 1 | John Doe |
| 2 | Not John Doe |
// roles
| id | slug |
| --- | ----- |
| 1 | admin |
| 2 | ga |
// role_users
| user_id | role_id |
| ------- | ------- |
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
I want to count users with admin role
User::whereHas('roles', fn ($query) => $query->whereSlug(Role::ADMIN()))->count()
It works, I get correct number but I need to do same query for another roles so this is where things become ugly. I want to avoid doing this
$a = User::whereHas('roles', fn ($query) => $query->whereSlug(Role::ADMIN()))->count(); // 2
$b = User::whereHas('roles', fn ($query) => $query->whereSlug(Role::MODERATOR()))->count(); // 10
$c = User::whereHas('roles', fn ($query) => $query->whereSlug(Role::GA()))->count(); // 12
$d = User::whereHas('roles', fn ($query) => $query->whereSlug(Role::CA()))->count(); // 85
At the end I need to get an array of numbers like [2, 10, 12, 85]. How can I do it using Laravel queries?
Was able to achieve similar with
$users_count = DB::table('role_users')
->select(DB::raw('count(*) as users_count, role_id'))
->groupBy('role_id')
->get()
->mapWithKeys(fn($item, $key) => [$item->role_id => $item->users_count])
->toArray();
Now I can access count array with array_values($users_count)

Laravel filter date range with date range columns

If the selected dates ($postFr, $postTo) are within defined periods, I want to list the related products. So can I do something like the following example?
example:
$q->whereBetween('start_date', array($postFr, $postTo));
$q->orWhereBetween('end_date', array($postFr, $postTo));
//Could there be a similar use like this???;
$q->whereBetween(array(start_date, end_date), $postFr);
$q->orWhereBetween(array(start_date, end_date), $postTo);
Detailed explanation:
I have a periods table like this
periods
| id | start_date | end_date | product_id |
|------|--------------|------------|------------|
| 1 | 2021-02-19 | 2021-03-21 | 1 |
| 2 | 2021-02-19 | 2021-03-21 | 2 |
| 3 | 2021-02-19 | 2021-03-21 | 3 |
| 4 | 2021-02-19 | 2021-03-21 | 2 |
and I have a products table like this
products
| id | name |
|-------|-----------|
| 1 | pro1 |
| 2 | pro2 |
| 3 | pro3 |
The relevant codes on my model page are as follows
public $belongsTo = [
'relationPeriod' => [
'Model->periods table',
'key' => 'id',
'otherKey' => 'product_id',
]
]
$query->whereHas('relationPeriod', function($q) use ($postFr, $postTo){
$q->whereBetween('start_date', array($postFr, $postTo]));
$q->orWhereBetween('end_date', array($postFr, $postTo));
});
it works
$q->whereDate('start_date', '<=', $postFr);
$q->whereDate('end_date', '>=', $postFr);
$q->orWhereDate('start_date', '<=', $postTo);
$q->whereDate('end_date', '>=', $postTo);
This will work:
$start_date = '2021-01-01';
$end_date = '2021-02-01';
$filter = Atom_juice_data_history::whereBetween('created_at',[$start_date, $end_date])
->where('atom_juice_data_id',$product_id)
->orderBy('created_at', 'ASC')
->get();
This is reference url
https://medium.com/#sectheater/how-to-use-laravel-wherebetween-method-elegantly-20a50f3b0a2a

How Products is order by relation in Laravel?

How can I sort the products in table1 according to their connection with table2 i.e. sort the products in table1 by example_relation in table2?
Table1
| id | product |
|----|---------|
| 1 | pro1 |
| 2 | pro2 |
| 3 | pro3 |
| 4 | pro4 |
| 5 | pro5 |
Table2
| id | example_relation | product_id |
|----|------------------|------------|
| 1 | 700 | 1 |
| 2 | 800 | 2 |
| 3 | 900 | 3 |
| 4 | 850 | 2 |
| 5 | 600 | 4 |
| 6 | 125 | 5 |
Table1 model page:
public $belongsTo = [
'pro_relation' => [
'models\Model',
'key' => 'id',
'otherKey' => 'product_id',
],
Sorting section that needs to be arranged correctly:
$query->whereHas('pro_relation', function($q) use ($postFrom,$postTo){
$q->select('example_relation END AS expose');
})->orderBy("expose", DESC);
Okay, when you want to order the result of one table from another table then you can use orderBy() inside a closure of the relationship to another table.
So from your query, you can add orderBy() like so.
$query->whereHas('pro_relation', function($q) use ($postFrom,$postTo){
$q->select('example_relation END AS expose')
->orderBy('example_relation');
});
This is work
$query->join('table2', 'table1.id', '=', 'table2.yacht_id')
->orderBy(example_relation DESC);

Laravel eloquent whereHas where first date equals a specific date

I have two related tables:
projects:
| id | name |
| 1 | Test |
| 2 | Example |
project_dates:
| id | project_id | date |
| 1 | 1 | 2020-02-01 |
| 2 | 1 | 2020-02-10 |
| 3 | 1 | 2020-01-25 |
| 4 | 2 | 2020-01-10 |
| 5 | 2 | 2019-12-15 |
Now for example I would like to get all projects where the first date of project_dates is equal to 2020-01-25. So I tried the following:
$projects = Project::whereHas('dates', function($query) {
$query->whereRaw("MIN(date) = '2020-01-25'");
});
But this returns the following error:
SQLSTATE[HY000]: General error: 1111 Invalid use of group function
which revers to MIN(date). So my question remains how can I get all projects with the first date being equal to 2020-01-25 or any other specific date. From the example above I would expect to get the project with id 1 since the first date of 2 is 2019-12-15
I know that is possible to use a subqueryjoin however I feel like doing 2 queries shouldn't be required.
$firstDates = ProjectDate::groupBy('project_id')
->selectRaw('project_id, MIN(date) as first_date');
Project::joinSub($firstDates, 'first_dates', function($join){
$join->on('first_dates.project_id', '=', 'project.id')
->where('first_date', '=','2020-01-25');
})->get();
You cannot use where aggregate function with groupBy, try to use havingRaw instead:
$projects = Project::whereHas('dates', function($query) {
$query->havingRaw("MIN(date) = ?", ['2020-01-25']);
});
// Or
$projects = Project::whereHas('dates', function($query) {
$query->having(DB::raw("MIN(date)"), '2020-01-25');
});
$projects = Project::whereHas('dates', function($query) {
$query->having(DB::raw("MIN(date)"), '2020-01-25')
->groupBy( 'project_dates.id' ); // For sql_mode=only_full_group_by
});

I need to write eloquent query that return messages having specific tag

messages Table
+----+----------+
| id | text |
+----+----------+
| 1 | message1 |
| 2 | message2 |
+----+----------+
tags table
+----+-----------+
| id | name |
+----+-----------+
| 1 | valentine |
| 2 | funny |
| 3 | santa |
+----+-----------+
message_tag
+----+------------+--------+
| id | message_id | tag_id |
+----+------------+--------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 2 | 2 |
+----+------------+--------+
I have written this query so far to get all messages but i cant return the
message where tag_id = 1;
Message::with([
'tags'=>function($q){
$q->select("name","tag_id");
}
])->select("id", "text")->paginate(10);
i have used where clause inside function($q){$q->where('tag_id',1)}. but it didn't worked.
You have to include use Illuminate\Support\Facades\DB; at the top of class
$tagId = 1;
$return = DB::table('message_tag')
->join('messages', 'messages.id', '=', 'message_tag.message_id')
->join('tags', 'tags.id', '=', 'message_tag.tag_id')
->where("message_tag.tag_id", "=", $tagId)
->select('messages.text', 'tags.text')
->get();
Try using whereHas(), assuming you set a many-to-many relationship properly:
$tag = 'Madhab452';
$messages = Message::whereHas('tags', function ($query) use ($tag) {
$query->where('name', '=', $tag)
->select('name', 'tag_id');
})->get();

Resources