Laravel Query Builder, Where based on condition? - php

I have a dropdown to select a parent, which is self-referencing to the Page. I want to limit the results of that dropdown, so that it won't allow me to nest a Page more than one level.
If I edit the 'Son' Page, and the 'Son' has a 'Grandson', than I shouldn't be allowed to select 'Dad' as parent for the son, since it would create a nest that is to deep
In the folllowing case, When I'm editing the Son record, I shouldn't be able to select Dad as it's parent, since the son has children.
+----+-----------+----------+
| id | parent_id | title |
+----+-----------+----------+
| 1 | NULL | Dad |
| 2 | NULL | Son |
| 3 | 2 | Grandson |
+----+-----------+----------+
Now in this case, I should be able to select Dad as the parent when I'm editing the Son record, since the Son doesn't have any children
+----+-----------+----------+
| id | parent_id | title |
+----+-----------+----------+
| 1 | NULL | Dad |
| 2 | NULL | Son |
| 3 | NULL | Grandson |
+----+-----------+----------+
I'm struggling to get my head around this, and on how to wrap this all in the query builder.
What I have so far
The following code works if the Son has a Child of it's own, I won't be able to select Dad, which is good. But it fails when there's no children.
It comes down to this: My parent select should also allow pages where parent_id is null to be shown, but only if the current record (Son) doesn't have any children.
Recap: Only show the record if it doesn't occur in any parent_id, so has no children, if it does however no records are to be shown.. If it doesn't, it should show the records where parent_id is null. Is this possible in one query?
$query->where('id', '<>', $page->id);
$query->where('parent_id', '<>', $page->id);

You can create parent and children relationship in the Page model.
public function parent()
{
return $this->belongsTo('Page', 'parent_id');
}
public function children()
{
return $this->hasMany('Page', 'parent_id');
}
Now, you can check if Page has children or not and the pick up the records with parent id null like this:
$pages = Page::where('parent_id', null)->doesntHave('children')->get();
This will give you records with parent id null and no children. I hope you understand.

You can achieve this with query builder sub-query with whereRaw function
Check the code
$result = DB::table('parents as p')
->whereRaw("parent_id is null and id not in (SELECT parent_id FROM parents WHERE parent_id is not null)")
->get();
dd($result);
The o/p looks like this
1st scenario
2nd Scenario
It may help you :)

You need to join the table on itself, as the where-clause is a per-row basis.
Join the table on itself with a left-join, where the ID matches the parent ID. Then select only the rows where the joined table's IDs are null.
$pages = DB::table('pages AS p')
->leftJoin('pages AS p1', 'p.id', '=', 'p1.parent_id')
->where('p.id', '<>', $page->id)
->whereNull('p1.id')
->select('p.*')
->get();
DB fiddle showing the actual query: https://www.db-fiddle.com/f/pwW1aPmYyVqvWjKgXhisbZ/2

Hmmm... I don't think it is possible to break this to just one line because am like you will have to loop the parent_id column checking whether the current id is in the parent_id column or not:
But not to worry: a different approach would be easier: two separate tables and thus different relationship:
Pages model: has one parent
Parents model: belongs to a page
With above relationship you it is easy using eloquent to get what has no parent by just checking where has not the given relationship:
Breaking this down:
Pages model:
public function parents () {
return $this->hasOne(Parent::class, 'page_id');
}
to parents model:
Parents model:
protected $fillable = ['page_id'];
public function pages () {
return $this->belongsTo(Page::class, 'page_id');
}

Related

Problem when getting records from database with relationship table

I'm having trouble relating photos to tags using an intermediate table.
In the example below, how can I select all photos that belong to tag 1 with an Eloquent relationship method in Laravel?
I have these tables:
-Photos Table
| id | name | description |
1 photo1.png ....
2 photo2.png ....
3 photo3.png ....
-Tags Table
| id | name |
1 Aesthetic
2 Dark
-Tags Relations
| id | tag_id | photo_id |
1 1 3
2 1 2
3 2 1
First of, you need to ensure that both Photos and Tags table have the relationship defined.
Under the Photos model you should have the following function:
public function tags() {
return $this->belongsToMany(
Tag::class,
"photos_tags", // the name of the pivot table
"photo_id",
"tag_id"
);
}
Under the Tags model you should have the following function:
public function photos() {
return $this->belongsToMany(
Photo::class,
"tags_photos", // the name of the pivot table
"tag_id",
"photo_id"
);
}
Now to access all the tags that are related to the Photo of id 1, you can call the following:
Photo::findOrFail(1)->tags()->get();
And the same you can do for a specific tag to get all it's photos.
Tag::findOrFail(1)->photos()->get();
Hope this will lead you to what you wish.

Eloquent sub-query for if the row is the latest one

I have the following subquery:
->whereHas('statuses', function($query){
$query->where('status_id', request('status'));
})->orderBy(...)->paginate();
For simplicity's sake I have hidden the full query. Let's say I have the pivot table user_status. User has many statuses and a status can also have many users (unusual, I know. Just consider it as an example). So when I query the pivot table user_status as described above, I get those rows that have the requested status id. But I also want to add the constraint that the row must be the latest one for that particular user (not in entire table). Here is how the user_status table looks like:
+---------+-----------+---------------------+
| user_id | status_id | created_at |
+---------+-----------+---------------------+
| 1 | 1 | 2018-09-03 18:39:14 |
| 1 | 8 | 2018-09-03 18:51:42 |
+---------+-----------+---------------------+
In this scenario, if the requested status is 1, I don't want this row to be returned, because status_id 1 is not the latest one for the user_id 1 (see the time difference in create_at column).
How can I achieve that?
You can achieve that by using another condition with a nested query.
->whereHas('statuses', function($query){
$query->where('status_id', request('status'))
->whereIn('user_status.id', function($query) {
$query->selectRaw('MAX(id)')
->from('user_status')
->groupBy('user_id');
});
})->orderBy(...)->paginate();
The nested query will select only latest rows(assuming user_status.id is auto-increment)
I define a relation in user model as :-
public function latest_statuses() {
return $this->hasMany(Status::class,'user_id','id')->orderBy('created_at','DESC')->take(1);
}
And after that written query as :-
$status = \App\User::with('latest_statuses')->whereHas('latest_statuses', function($query){
return $query->where('status_id', 1);
})->get();
It works for me.

4 level laravel eloquent relationship

I have four tables and i want to make a 4 level relationship. My table looks like this:
------------|------------|------------|------------|
City | Category |Subcategory | Company |
------------|------------|------------|------------|
id | id | id | id |
name | name | name | name |
------------|------------|------------|------------|
I did something like this:
City.php
public function categories()
{
return $this->belongsToMany(Category::class);
}
Category.php
public function subcategories()
{
return $this->belongsToMany(Subcategory::class);
}
Subcategory.php
public function category()
{
return $this->belongsTo(Category::class);
}
But it's not giving the result I need. I have many cities that can have many companies by their category and subcategories. For example I can save city_id, category_id, subcategory_id to company table and get the right companies doing the query:
$company->where('city_id', 1)->where('category_id', 2)->where('subcategory_id', 3)->get();
But I think it's not a best practice. I hope somebody helps. Thanks.
The main question is how can i make a normal relationship from this query:
I have many Cities that can has many categories that has many Subcategories and the subcategories i have companies for the specific city->category->subcategory.
For example in Company model i have city_id, category_id, subcategory_id and when i'm fetching categories for the specific city there will subcategories and in subcategories will be companies.
So the query will be something like:
App\Company::where('city_id', 1)->where('category_id', 2)->where('subcategory_id, 3)->get();
Sorry for bad explanation. I hope you get it...

Laravel (5.3) Eloquent - Relationship issue

I have the following 3 tables which are normalised:
`Table: TheMovies`
id | MovieName
---------------------
1 | Zootopia
2 | Moana
3 | Toy Story
`Table: TheGenres`
id | GenreName
---------------------
21 | Action
22 | Animation
23 | Adventure
`Table: mMoviesGenres`
movieID | genreID
---------------------
1 | 21
1 | 23
2 | 22
2 | 21
3 | 23
3 | 21
As you can see in the 3rd table a movie has multiple genres, and a genre has multiple movies.
I've created TheMovies and TheGenres models in laravel.
I made sure that the relationship is made inside the models using the following code:
class TheMovies extends Model
{
public function TheGenres() {
return $this->belongsToMany('App\TheGenres', 'mMoviesGenres', 'seriesID', 'genreID');
}
}
class TheGenres extends Model
{
public function TheGenres() {
return $this->belongsToMany('App\TheMovies', 'mMoviesGenres', 'genreID', 'seriesID');
}
}
I've tested everything, and I succeeded displaying a list of genres for a particular movie, and I also succeeded displaying a list of movies for a particular genre.
The actual problem is that I want to display related movies for a particular movie based on genre.
Let's take TheMovies.id = 1 which is similar with TheMovies.id = 3, they are both Action and Adventure as you can see in the third table.
I've found out the query which is needed based on the following post:
SQL Query based on other table.
SELECT m2.movieId
FROM mMoviesGenres m1
INNER JOIN mMoviesGenres m2
ON m1.genreID = m2.genreID
WHERE m1.movieId = 1 AND
m2.movieId <> 1
GROUP BY m2.movieId
HAVING COUNT(*) >= 2
But I don't know how to transform this query in Eloquent style, and yes I can make a raw query in Eloquent, but I want to make use of the relationship created.
Please give me some advice.
You can try as:
// returns array of genre_ids associate with the TheMovies.id => 1
$genre_ids = TheGenres::whereHas('TheMovies', function($q) {
$q->where('id', 1);
})->pluck('id')->toArray();
Then use those $genre_ids to fetch the related movies as:
TheMovies::whereHas('TheGenres', function($q) use($genre_ids) {
$q->whereIn('id', $genre_ids);
})->get();
Update
Assuming you have:
$genre_ids = [21, 23];
then your query can be as:
TheMovies::whereHas('TheGenres', function($q) use($genre_ids) {
$q->whereIn('genreID', $genre_ids)
->groupBy('movieID')
->havingRaw('COUNT(DISTINCT genreID) = 2');
})->get();
Note - I have not tested it but you can give it a try.

Laravel 4 relational database query

can anybody help me on the following query.
I have a table that holds a postcode column and a rating column, ie.
ID | POSTCODE | RATING
1 | sk101dd | E
2 | sk101de | A
3 | sk101df | E
4 | sk101dg | E
5 | sk101dh | D
etc
This is set up as a model called PostcodeList
I have a relational table, linked via the RATING column that holds a customer id and cost, ie.
ID | CUSTOMER_ID | RATING | COST
1 | 1234 | E | 0.05
2 | 9999 | E | 0.02
This is set up as a model called RatingCost. I linked this to PostcodeList model using the following code:
public function costing()
{
return $this->hasMany('PostcodeList','RATING','RATING');
}
I need to return the COST from the RatingCost model using CUSTOMER_ID as the filter without resorting to multiple sql statements. I think I've nearly got it, using the code below, but it's not quite right:
$p = PostcodeList::where('postcode',$somepostcode)->first();
$cost = $p->costing()->where('customer_id',$somecustomerid)->first()->cost;
The error I'm getting at the moment is "Trying to get property of non-object".
Any help greatly appreciated. I don't want to resort to DBRAW or another form of join as I really like the relational setup Laravel provides.
thanks
I know you're trying to stay away from joins, but this Laravel query would produce the desired results:
DB::table('PostcodeList')
->join('RatingCost', function($join)
{
$join->on('RATING', '=', 'RatingCost.RATING')
->->where('customer_id',$somecustomerid)
})
You have this
$postcode_get = PostcodeList::where('postcode',$somepostcode)->get();
foreach($postcode_get as $p){
...
$cost = $p->costing()->where('customer_id',$somecustomerid)
// ...
}
You have defined the method costing in your RatingCost model but calling it from PostcodeList model, in this case you need to declare the relation inside your PostcodeList model like this:
public function costing()
{
// change the field name 'RATING' in any table, maybe
// prefix with table name or something else, keep different
return $this->belongsToMany('RatingCost','RATING', 'RATING');
}
So, you can use this (inside loop):
$cost = $p->costing();
Because, inside your loop each $p represents a PostcodeList model and $postcode_get is a collection of PostcodeList models.
I don't think what I'm trying to do is actually possible without using joins. So the solution was to scrap the belongsTo and hasMany options for a standard join (similar to dev_feed's response):
$pr = PostcodeList::join('RatingCost', function($join)
{
$join->on('PostcodeList.content_rate', '=', 'RatingCost.code');
})
->where('postcode', '=', $somepostcode)
->where('RatingCost.customer_id','=',$somecustomerid)
->first();

Categories