I have two related tables:
publications:
| id | name |
| 1 | Test |
| 2 | Example |
publication_dates:
| id | publication_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 publications where the first date of publication_dates is after 2020-01-01. So I tried the following:
$publications = Publication::whereHas('dates', function($query) {
$query->whereRaw("MIN(date) > '2020-01-10'");
});
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 publications with the first date being after 2020-01-01 or any other specific date. From the example above I would expect to get the publication with id 1 since the first date of 2 is 2019-12-15
You should reverse querying logic and then you can use relationship absence.
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
$date = Carbon::create('2020-01-10');
$publications = Publication::whereDoesntHave('dates', function(Builder $query) use ($date) {
$query->where('date', '<', $date->toDateString());
});
You can also go further and make local scope method for query builder in model
// Publication model
/**
* Scope a query to only include publications that has date newer than $date
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param string|object Carbon $date
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeDatesNewerThan($query, Carbon $date)
{
return $query->whereDoesntHave('dates', function(Builder $query) use ($date) {
$query->where('date', '<', $date->toDateString());// or set '<=' if more appropriate
});
}
Then somewhere in code i.e. newer than last 30 days
// $publications = Publication::datesNewerThan(Carbon::now()->subDays(30))->get();
I assume the error is because you are using an aggregate function without a proper group by clause, You could use a join to tackle this. but here is a slightly different approach using the subquery join.
$firstDates = PublicationDate::groupBy('publication_id')
->selectRaw('publication_id, MIN(date) as first_date'); // assuming you have a model called PublicationDate for the publication_dates table
Publication::joinSub($firstDates, 'first_dates', function($join){
$join->on('first_dates.publication_id', '=', 'publications.id')
->where('first_date', '>','2020-01-10');
})->get();
Related
I have 4 tables named: categories, products, blogs, companies.
+-----------+
| Category |
+----+------+
| id | name |
+----+------+
| 1 | Cat1 |
| 2 | Cat2 |
+----+------+
+-----------+
| Company |
+----+------+
| id | name |
+----+------+
| | |
+----+------+
+-------------------------+
| Product |
+----+-------------+------+
| id | category_id | name |
+----+-------------+------+
| 1 | 1 | P1 |
| 2 | 2 | P2 |
| 3 | 1 | P3 |
+----+-------------+------+
+---------------------------+
| Blog |
+----+------------+---------+
| id | product_id | heading |
+----+------------+---------+
| 1 | 1 | H1 |
| 2 | 2 | H3 |
| 3 | 3 | H4 |
+----+------------+---------+
Blog Model
public function product()
{
return $this->belongsTo(Product::class);
}
Product Model
public function company()
{
return $this->belongsTo(Company::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
Blog::with('product.category')
->where('status', 'Y')
->where('featured_position', 'Y')
->orderBy('id', 'DESC')
->get();
From the above tables the result will show 2 blogs namely blogs having id 1 and 3. But the above code is fetching result for all the blogs from the blog table.
You'll want to use a whereHas to query the relationship.
$categoryId = 1;
$productQuery = function ($query) use ($categoryId) {
// This $query object will be for the Product models, so we can treat it as
// such.
// We can query like we would on a Product, like Product::where([...]).
$query->with('category')->where('category_id', $categoryId);
};
$blogs = Blog::whereHas('product', $productQuery)
->with(['product' => $productQuery])
->get();
I've set the category ID to a variable, in case you need to change it during runtime.
Also, note that the with is completely optional.
If you exclude it, your query will run exactly the same, just without constrained eager loading. The effects of this are just that you will have to perform more database requests. The benefits come if you never actually need the relationship, then it won't have been fetch unnecessarily.
If you're curious what the SQL command will be, it will be:
SELECT * FROM `blogs`
WHERE EXISTS (
SELECT * FROM `products`
WHERE `blogs`.`product_id` = `products`.`id` AND `category_id` = ?
)
In simple terms, it will select everything from the blogs table.
It's then going to query the products table, using an inner join, to select products that have a corresponding blog entry.
The second part of the where clause is going to just get the specific character. The ? is because category_id can be any integer.
Catory with id 1
fetch its products
fetch blogs for each of its products ( map over products )
flatten the results ( since its gonna be nested for each product )
Category::find(1)->products->map->blogs->flatten();
you can use Tinker to interact with you application's query builder and eloquent models from terminal you can use :
$ php artisan tinker
For more clause you can use collection methods :
Category::find(1)->products->map->blogs->flatten()->where('status', 'Y')
->where('featured_position', 'Y')
->sortDesc('id') ;
I'm newer to Laravel and trying to use ORM to filter my results. So in my database I have table one (Developers) and table two (Programs):
Developers
-------------------
ID. | Name |
------|------------
1 | Bluegreen |
2 | Dancer |
3 | Martin |
------|------------
Programs
-------------------------------------
id | developer_id | name |
------|------------------------------
1 | 12 | Program Test |
2 | 3 | Capital |
3 | 2 | Asus |
4 | 2 | Rich |
5 | 5 | Huna |
------|------------------------------
I'm trying to filter by the Program name and my code is filtering by Developer name. I can easily do this in raw sql but I'm trying to learn ORM, for some reason it's like super difficult for me to learn.
Here's my code:
$query = $request->get('search');
$developers = Developer::where('name', 'LIKE', '%'.$query.'%')->with('programs')->get();
I think this code will work.
DB::table('developers')
->join('programs','developers.id','=','programs.developer_id')
->where('programs.name', 'like', '%'.'Capital'.'%')
->select('programs.*','developers.name')
->get();
You can simply do this in a closure function in laravel with relationships. This will filter your record on program name and also developer name.
$developers = Developer::where('name', 'LIKE', '%'.$request->search.'%')
->with(['programs' => function($filter) use ($request) {
$filter->where('name','LIKE','%'.$request->search.'%');
}])->get();
If you want to only filter by program name then you can do it in this way
$developers = Developer::with(['programs' => function($filter) use ($request) {
$filter->where('name','LIKE','%'.$request->search.'%');
}])->get();
You can also write a condition if $request->search is empty then return all data. If it is not empty then only return the filtered data. I am just simply doing it but you can optimize the query later.
if($request->search != '')
$developers = Developer::with(['programs' => function($filter) use ($request) {
$filter->where('name','LIKE','%'.$request->search.'%');
}])->get();
else
$developers = Developer::with('programs')->get();
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
});
Currently having trouble of using groupBy in nested relation on laravel. I have 3 Tables and I want to group the result base on the CountryTbl value. Here are the tables.
UserTbl
----------------------------------
id | name | branch_id |
----------------------------------
1 | Joseph | 1 |
2 | Manuel | 1 |
3 | Margaret | 3 |
----------------------------------
BranchTbl
----------------------------------
id | branch_name | country_id |
----------------------------------
1 | Pampanga | 1 |
2 | Manila | 1 |
3 | California | 2 |
----------------------------------
CountryTbl
------------------------
id | country_name |
------------------------
1 | Philippines |
2 | United States |
------------------------
This is my Model
UserModel
public function branch()
{
return $this->belongsTo('App\Branch');
}
BranchModel
public function country()
{
return $this->belongsTo('App\Country');
}
CountryModel
Now, In the table shown above, I want to get all the users and group them by country.
Here is what I've tried.
public function getAllUsers(){
$users = User::with('branch')
->with(['branch.country' => function($q){
return $q->groupBy('country_name');
}])
->get();
return $users;
}
My code doesn't work. It always returns me an error saying:
Syntax Error or Access Violation
Try this:
User::with(['branch', 'branch.country' => function($q) {
return $q->groupBy('CountryTbl.country_name');
}])
or
User::with(['branch', 'branch.country'])
->get()
->groupBy('branch.country.country_name');
You may try the below code
$data = DB::table('country')
->join('branch', 'country_id', '=', 'country.id')
->join('user', 'branch_id', '=', 'branch.id')
->select('country.country_name','user.name','branch.branch_name')
->groupBy('country.country_name')
->orderBy('country.country_name','ASC')
->get();
I got a problem, When try to use whereHas in this case
Table users.
----------------
| id | name |
----------------
| 1 | AAA |
| 2 | BBB |
| 3 | CCC |
----------------
Table subjects.
------------------
| id | title |
------------------
| 1 | Subject 1 |
| 2 | Subject 2 |
------------------
Table subject_user.
------------------------
| user_id | subject_id |
------------------------
| 1 | 1 |
| 2 | 1 |
| 1 | 2 |
| 3 | 2 |
------------------------
in Subject Model
...
public function User()
{
return $this->belongsToMany(User::class, 'subject_user');
}
...
When I want to find subject by user_id with this query.
In this case Auth::id() == 1 and $request->user_id == 3
$subject = Subject::whereHas('User', function ($query) use ($request) {
$query->whereIn('user_id', [Auth::id(), $request->user_id]);
})->get();
With this query, I got subjects 1 and 2. That was a wrong result. That must got only subject 2.
Then I try this.
$subject = Subject::whereHas('User', function ($query) use ($request) {
$query->where('user_id', Auth::id())->where('user_id', $request->user_id);
})->get();
It would not get any subjects.
What query do I use in this case to get only subject 2.
#Lloople from your answer, I got an idea.
$subject = Auth::user()->Subject()->whereHas('User', function ($query) use ($request) {
$query->where('user_id', $request->id);
})->first();
Why not doing it backwards? You already have the logged in user, so if you define the relationship in the User model you can do
$subjects = auth()->user()->subjects;
Anyway, you don't need to check double the Auth::id()and $request->user_id. In fact, I'm not sure you can do this last one.
Edit after comments
$subjects = Subject::whereBetween(auth()->user()->id, $request->user_id)->get();
You will need to change the order from the inputs, in case $request->user_id is less than auth()->user()->idor it wouldn't work.