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

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.

Related

One-to-many Eloquent relation: Query to filter based on related table's column values

I have two tables that look like this:
stocks:
id | name | market_cap
1 | Microsoft | 5000
2 | Tesla | 2000
This table has a one-to-many relationship with a table titled stock_ratings:
id | stock_id | measure | value
12 | 1 | revenue | 30
13 | 1 | dividend| 5
14 | 2 | revenue | 10
15 | 2 | dividend| 0
Now, let's say a user wants to get all stocks with market_cap > 3000, skip the first 20 results, and get the next 20. This works:
$query = Stock::where('market_cap', '>', 3000);
$stocks = $query->skip(20)->take(20)->get();
However, the user needs to also be able to query on the stock_ratings. So, let's say, in addition to the conditions above, he wants to find all stocks that have a value > 20 for the revenue measure.
I have tried this, and it doesn't work. It returns the same list of stocks as it would without this additional condition.
$query = Stock::where('market_cap', '>', 3000);
$query = $query->with(['ratings' => function ($q) {
$q->where('stock_ratings.measure', 'revenue');
$q->where('stock_ratings.value', '>', 0);
}]);
$stocks = $query->skip(20)->take(20)->get();
This returns all stocks with market_cap > 3000 regardless of the value and measure in the stock_ratings table. It returns stocks that have a negative value in stock_ratings where measure = revenue and it also returns rows that have no related rows in stock_ratings whatsoever.
How do I resolve this?
When you apply a constraint in with it means the constraint is applied for which nested related records should be eager loaded and which should not be eager loaded.
However in your case you want to filter or apply constraint on the stocks based on values contained in related ratings.
So try this
$query = Stock::where('market_cap', '>', 3000);
$query = $query->whereHas('ratings', function ($q) {
$q->where('measure', 'revenue');
$q->where('value', '>', 0);
})-with('ratings');
$stocks = $query->skip(20)->take(20)->get();
Read more at Laravel docs: https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence

How to get all rows from database by a foreign key value with Eloquent?

I use Laravel on my website and Eloquent to manage my database. I have users with roles. This looks like this in my database :
|USERS |
|----------------------------------|
| ID | EMAIL | ... | ROLE_ID | ... |
|ROLES |
|------------|
| ID | LABEL |
I would like to select user by their role label.
I tried to use joins and where functions but I don't know why the returned users have their ID set with their ROLE_ID.
$admins = DB::table('users')
->leftJoin('roles', 'users.role_id', '=', 'roles.id')
->where('roles.label','=', 'admin')
->get();
Here is one of the user returned :
object(stdClass)[285]
public 'id' => int 3
...
public 'role_id' => int 3
...
The ID of this user is 2 in the database. If I change it's ROLE_ID to 1, it's ID also change to 1.
I looked for solutions on the internet but I don't really know what to look for so I did not find anything.
I am sure this is quite simple but I am stuck anyway.

laravel table structure showing the most liked articles and less views

users can add articles and other users can like or dislike I need most liked with the less view
example :
article (A) has 500 views and 10 like - article (B) has 50 views and 10 likes
the order will be (B) than (A)
This is my database structure and I join them together:
articles table:
id | user_id | title | description | views | created_at | updated_at
likes table:
id | user_id | article_id | like_type | created_at | updated_at
query to get likes count :
Article::leftJoin('likes', 'likes.article_id', '=', 'articles.id')
->select('articles.*', DB::Raw('SUM( 2 * likes.like_type -1) as likes_count'))
->groupBy('articles.
->get();
how can i order ?
You can order by multiple columns in laravel by chaining orderBy.
In your case for example you will want to first order by "likes_count" descending (=biggest first) and then by views ascending (= smallest first, this is the default value).
So you will do:
return Article::select('title', DB::Raw('SUM(2*likes.like_type-1) as likes_count'))
->leftJoin('likes', 'likes.article_id', '=', 'articles.id')
->groupBy('articles.id')
->orderBy('likes_count', 'DESC')->orderBy('views')
->get();

Yii2: ActiveRecord with distinct query

I need to write an ActiveRecord query where I get all fields of rows without duplicates in one field.
Example: Here is a table of books. I want to get all data of the rows with
distinct isbn. From duplicates the first row should be taken. The result should be the rows with id 1,2,4
id | title | isbn
--- | ------- | ------
1 | hello | 1001
2 | world | 1002
3 | this | 1002
4 | is | 1003
5 | funny | 1003
My first attempt was
$books = Books::find()->select('isbn')->distinct()->all();
but that fills only the isbn field in $books[0], $books[1], ....
Is this possible using Yii2 ActiveRecord?
You may use groupBy() for this:
$books = Books::find()->groupBy(['isbn'])->all();
But this returns random row with matching isbn value and may not work in some DBMS. Your requirements are too ambiguous to handle it in predictable way. But if you want always first row, you may use subquery to fetch matching isbn and id:
$query = (new Query())
->select(['MIN(id) as id, isbn'])
->from(Books::tableName())
->groupBy(['isbn']);
return Books::find()
->alias('book')
->innerJoin(['subquery' => $query], 'book.id = subquery.id AND book.id = subquery.id')
->all();
The description given in Yii2 for the distinct property is as follows:
Whether to select distinct rows of data only. If this is set true, the SELECT clause would be changed to SELECT DISTINCT.
-- https://www.yiiframework.com/doc/api/2.0/yii-db-query#$distinct-detail
therefore you should pass true with distinct property if you need to select the distinct values of 'isbn' as follows:
$books = Books::find()->select('isbn')->distinct(true)->all();

Laravel 2 table join with all from left table merged with members of the right table with a given FK value

I have two tables in Laravel of which I am seeking to merge them together, however, I want to return every single value of the first table (without duplicates) along with only values from the second table that have a FK value of 2. If there is no entry with a FK of 2, it joins with a value of null.
To make my question a little more clear, lets say we have the following tables:
TV Shows Table
ID | Show
1 | First Show
2 | Second Show
3 | Third Show
Favorites Table
Show_ID | Member_ID
1 | 1
3 | 1
1 | 2
2 | 2
I am looking to merge them into a resultant set like the following when I join the tables with a member ID of 2(disregarding the joined 'Show_ID' column):
Merged Table
ID | Show | Member_ID
1 | First Show | 2
2 | Second Show | 2
3 | Third Show | null
Thanks.
Not 100% I understood, so hope this is what you're looking for.
I've renamed some of the column names to make things a little clearer.
DB::table('shows')
->select(
'shows.id as show_id',
'shows.name as show_name',
'member.id as member_id'
)
->leftJoin('favourites', 'shows.id', '=', 'favourites.show_id')
->get();
Left join will allow there to be null in member_id if it isn't present on the join.
You can add this to restrict to member ID of two:
DB::table('shows')
->select(
'shows.id as show_id',
'shows.name as show_name',
'member.id as member_id'
)
->leftJoin('favourites', 'shows.id', '=', 'favourites.show_id')
->where('member.id', 2)
->get();
I solved it myself. I needed to do a functional join like so:
DB::table('shows')
->leftJoin('favorites', function($q) use $memberID)
{
$q->on('shows.ID', '=', 'favorites.show_ID')
->where('favorites.member_ID', '=', $memberID);
})->get();

Categories