Grouping BY Same Fields and Showing As 1 Division - php

I have a collection returning messages, however, I want to group them by 'subjects' and 'sender' to create 1 div for each subject, and show the latest one's created_at.
So I have:
id | senderId | receiverId | subject | message | created_at
My Controller:
$messages = Message::where('receiverId', \Auth::user()->name)
->orderBy('created_at','desc')
->paginate(10);
return view('chat')->with(compact('messages'));
So this works: dd($messages[0]->subject). Also, in my view, I can use #foreach to use it as $message->created_at. Currently it seems like this (subjects are not listed):
*yIZgT0oqH - is name
How can I group them by subject and senderId, and show them as 1 group(1 div) by using the latest message (created_at)? What is the best way?

You can modify your query to do something like this, using groupBy / aggregation.
$messages = Message::where('receiverId', \Auth::user()->name)
->groupBy('subject')
->orderBy('created_at','desc')
->paginate(10);
Check the documentation for the same - http://laravel.com/docs/5.1/queries#ordering-grouping-limit-and-offset

Related

Any idea how to Convert this to Laravel Query Builder

DB::select("select encoded_datas.* from encoded_datas inner join
(select data_user_firstname, data_user_lastname, data_user_barangay from encoded_datas t
group by data_user_firstname, data_user_lastname, data_user_barangay
having count(*)>1) t1
on encoded_datas.data_user_firstname=t1.data_user_firstname and encoded_datas.data_user_lastname=t1.data_user_lastname and encoded_datas.data_user_barangay=t1.data_user_barangay");
It's a working piece of query code, but it is so slow to load compare to Eloquent/Proper Query Builder.
Can anyone here help me convert this to Laravel's Query Builder format..
try this solution
DB::connection(your_connection)
->table('encoded_datas')
->select('encoded_datas.*')
->join(
DB::raw("SELECT data_user_firstname, data_user_lastname, data_user_barangay FROM encoded_datas t GROUP BY data_user_firstname, data_user_lastname, data_user_barangay HAVING COUNT(*) > 1) t1"),
function($join {
$join->on('encoded_datas.data_user_firstname', '=', 't1.data_user_firstname')
->on('encoded_datas.data_user_lastname', '=', 't1.data_user_lastname')
->on('encoded_datas.data_user_barangay', '=', 't1.data_user_barangay');
})
)
but I don't think it will speed up execution
Sometimes sending multiple requests might be less expensive then sending one nested.
You can try getting the ids of the duplicate rows first
$ids = DB::table('datas')
->select('firstname','lastname', DB::raw('COUNT(*) as `count`'))
->groupBy('firstname', 'lastname')
->havingRaw('COUNT(*) > 1')
->pluck('id');
Then get datas
$datas = Datas::whereIn('id', $ids)->get();
In your example you are executing two queries and a join.
Here we are executing 2 queries only, 1 of which uses primary key index, which should be instant. Technically its should be faster, looks cleaner as well
I've tried Alexandr's code:
$ids = DB::table('datas')
->select('firstname','lastname', DB::raw('COUNT(*) as `count`'))
->groupBy('firstname', 'lastname')
->havingRaw('COUNT(*) > 1')
->pluck('id');
$datas = Datas::whereIn('id', $ids)->get();
But it only shows single data/record.. What i want to get is something like this:
**id** |firstname |lastname
1 | John | Legend
2 | john | Legend
3 | Michael | Mooray
4 | Smith | West
5 | smith | West
so Im expecting to get the following result:
john |Legend
john |Legend
Smith |West
Smith |West

why laravel is not returning identify of a specific id

I have a problem in Laravel where I want to with a specific package to show something to these users?!
This is the existing code:
$package = DB::table('users')->where('package_id', '=', '2')->value('*');
And this brings me back all users with all the packages, but I just want the user with package number 2 and show something??
This is a query on the controller.
On the frontend, I just examined the variable.
Thanks guys
$package = DB::table('users')->where('package_id', '=', '2')->get() should suffice.
I can't see the advantage of appending value('*') in this scenario
Try this... I hope it will work sure
$package = DB::table('users')->select('*')->where('package_id', '=', '2')->get();
And this brings me back all users with all the packages, but I just
want the user with package number 2
I assume you mean you only want to get users with package_id 2 and nothing else.
If so the needed SQL query is harder.
Note that the query and demo is based on the information you have provided which is not alot
Query
SELECT
*
FROM
users
WHERE
users.id IN (
SELECT
users.id
FROM
users
GROUP BY
users.id
HAVING
SUM(users.package_id = 2) = 1
AND
COUNT(users.package_id = 2) = 1
)
Result
| id | package_id |
| --- | ---------- |
| 1 | 2 |
| 3 | 2 |
Also note that this query assumes that package_id's within user_id's are unique.
see demo
With DB Facade
$packages = DB::table('users')->where('package_id', '=', '2')->get();
With Eloquent
$packages = User::where('package_id', '2')->get();
Blade
#if($packages->count() > 0) //or #if(!$packages->isEmpty())
// if part
#else
// else part
#endif
Remember get() method always return collections
first() method return instance of a model
If you are using first() method instead of get() then you can handle something like that
Controller
$package = User::where('package_id', '2')->first();
Blade
#if($package) //or #if(!is_null($package))
// code...
#endif

Count does not work as expected in Laravel Query Builder

I am trying to get how many names do I have in database. For this purpose I am using Query Builder like this:
$namesIdsCount = DB::table('names_to_options')
->select('name_id')
->groupBy('name_id')
->havingRaw($having)
->count();
Is says that 24, which is not correct, because if I will write code like this:
$namesIdsCount = DB::table('names_to_options')
->select('name_id')
->groupBy('name_id')
->havingRaw($having)
->get();
result object contains 247 elements, which is correct. I have tried to play with skip/take, but still no results. Where am I wrong? Thanks for any help.
I think it's the other way around, you're not getting 24 groups. You're getting 24 elements within the first group. That configuration results in the following query:
SELECT
COUNT(*) AS 'aggregate',
`name_id`
FROM `names_to_options`
WHERE EXISTS(
{your $havingRaw sub-query}
)
GROUP BY `name_id`;
What you end up with will look something like this:
+---------------+---------+
| aggregate | name_id |
+---------------+---------+
| 24 | 1 |
+---------------+---------+
| 5 | 2 |
+---------------+---------+
| 30 | 3 |
+---------------+---------+
| ... and so on | 4 |
+---------------+---------+
Query\Builder just doesn't realize you can get more than one result back when count() is involved.
You were pretty close to the right answer yourself though.
$namesIdsCount = DB::table('names_to_options')
->select('name_id')
->groupBy('name_id')
->havingRaw($having)
->get();
get() returns an Eloquent\Collection, child of Support\Collection, which has its own version of the count method. So your answer is just:
$namesIdsCount = DB::table('names_to_options')
->select('name_id')
->groupBy('name_id')
->havingRaw($having)
->get()
->count();
If you really want this to happen in MySQL, the query you want to happen would look like this:
SELECT COUNT(*) FROM (
SELECT
`name_id`
FROM `names_to_options`
WHERE EXISTS(
{your $havingRaw sub-query}
)
GROUP BY `name_id`
) AS temp;
For that, you can do this:
$query = DB::table('names_to_options')
->select('name_id')
->groupBy('name_id')
->havingRaw($having);
$sql = $query->toSql();
$values = $query->getBindings();
$count = DB::table(DB::raw('('.$sql.') AS `temp`'))
->selectRaw("COUNT(*) AS 'aggregate'", $values)
->first()
->aggregate;
MySQL performance can get a little hairy when asking it to write temp-tables like that though, so you'll have to experiment to see which option is faster.
Inuyaki is right
(id, name_id),
(1,1),
(2,1),
(3,2),
(4,3)
There are are four rows so get() method will return 4 rows
but there are three groups if you use groupBy [name_id]
1 (1,1)
2 (2)
3 (3)
now count will return 3
hope this will help.

Laravel filter based on has one relationship

I am pulling my hair out over this one, and I feel like I have tried every method!
Here is my problem.
I have 2 tables
USERS
ID | FIRSTNAME | EMAIL_ADDRESS
1 | Joe Bloggs | joe#bloggs.com
STATUS
ID | USER_ID | STATUS | DATE
1 | 1 | 'In' | 2018-06-04 09:01:00
2 | 1 | 'Out' | 2018-06-04 09:00:00
As you can see by the tables above, each user can have many status', but each user has to have 1 most recent status, which I am doing like this (please tell me if I am doing it wrong)
public function statusCurrent(){
return $this->hasOne('App\Status', 'user_id', 'id')->orderBy('date', 'desc')->limit(1);
}
I then a form on in my view, which passes filters to the controller via a $request.
I need to be able to use the filters, and apply them to the 1 most recent status. For example, if someone searches for the date 2018-06-04 09:00:00 and a user id 1, I need it to show NO RESULTS, because the 1 most recent record for that user does not match that date, but at the moment, it will just jump over the one most recent if it doesn't match, and get the next record that does.
I have tried what seems like every method, I have tried like this
$users = Users::with(['StatusCurrent' => function($query){
$query->where('status.status', 'In');
}])->get();
Which gets the correct most recent row, but then if i try status.status, 'out' instead, it just jumps over and gets record number 2 where the status is out.
I've also tried like this
$users = Users::has('StatusCurrent')->paginate(10);
if(!empty($request->statusIn)){
$users = $users->filter(function ($item){
$item = $item->statusCurrent->status == 'in';
return $item;
});
}
return $users;
Which works great but then the pagination breaks when trying to append any GET parameters for the filters.
Plain English
I need to be able to get the most recent status for the user, then once I have it, I need to be able to apply where statements/filters/arguments to it, and if they don't match, completely ignore that user.
You have to combine a JOIN with a subquery:
$users = User::select('users.*')
->join('status', 'users.id', 'status.user_id')
->where('status.status', 'in')
->where('status.id', function($query) {
$query->select('id')
->from('status')
->whereColumn('user_id', 'users.id')
->orderByDesc('date')
->limit(1);
})
->get();
You can get the ID first, then do your query with filters:
$status_id = Users::find($user_id)->statusCurrent()->id;
Now do the actual query, using $status_id in whereHas clause:
$users = Users::with('statusCurrent')
->whereHas('statusCurrent', function($query) use($status_id) {
$query->where('status.status', 'In')
->where('id',$status_id);
})->get();
The relationship should be like:
public function statusCurrent(){
return $this->hasOne('App\Status', 'user_id', 'id')->latestOfMany();}

MySQL: How do I retrieve n (number of) data from an array?

I have a messages table.
+----+---------+----------+
| id | conv_id | body |
+----+---------+----------+
| 1 | 1 | haha |
| 2 | 1 | blabl|
| ...| ... | ... |
| 25| 2 | hehe |
+----+---------+----------+
... = rest of messages with conv_id of 2's or 1's or 3's or n's.
Let's say I have conv_id = array(2,1) and I want to obtain 10 messages after matched with an array of ids in conv_id so I did
select * from `messages` where `conv_id` in (2, 1) order by `created_at` desc limit 10
The sql above gave me 10 messages after matching both conv_ids and getting all combined messages. However, this is NOT what I wanted. Instead, I wanted 10 messages of EACH conv_id matched.
How do I get 10 messages of EACH conv_id matched? No PHP for loop, please. Thank you!
NOTE : the array conv_id can easily be extended to include many other values unique to each other, not only 2s or 1s.
P.s., bonus points for Laravel Eloquent answer! Here are the details :
Two models, Conversations and Messages linked by Conversations hasMany Message and Message belongsTo a Conversation.
My sql above was translated from Messages::with('User')->whereIn('conv_id',$conv_id)->orderBy('created_at','desc')->take(10);
I think Jared is right but if you can add another column to the table, solution would be more efficient. Add a column which indicates message number for each conv_id (earliest will have 1 and the latest will have number of messages conversation have). After that, you can achieve your goal by scanning table twice with HAVING clause.
SELECT * FROM messages JOIN
(SELECT conv_id, MAX(msg_no) FROM messages WHERE conv_id IN (2,1) GROUP BY conv_id) as M
ON messages.conv_id=M.conv_id HAVING messages.msg_no > M.msg_no-10
Another possibility. Get last conv_ids and their 10th (message)id with a group_concat - substring_index trick and re-join of message-table.
SELECT `messages`.*
FROM (
SELECT
conv_id,
substring_index(
substring_index(
group_concat(`messages`.id),
',',
10
),
',',
-1
) AS lastMessageId
FROM `messages`
WHERE `conv_id` in (2, 1)
GROUP BY `conv_id`
) AS msub
INNER JOIN `messages` ON `messages`.conv_id = msub.conv_id AND `messages`.id <= msub.lastMessageId
Warning: This approach is potentially wrong. I'm trying to validate it and will update it once I reach a conclusion
Yesterday I just learnt something new about Eloquent relationship from an answer by deczo in Getting just the latest value on a joined table with Eloquent. And I think we can adapt it to your case as well.
What you're essentially trying to do, I put in my view as:
A Conversation model that has many Messages.
But I want only 10 latest messages per each conversation. All come eager-loaded.
In Eloquent, I would probably do something along the line of:
Conversation::with('messages')->whereIn('conv_id', [1, 2])->get();
But two things I note of your question.
I assume you don't have another table called conversations.
The code above does not limit to how many messages per conversation.
So I decided that here should be two models, one called Message, another called Conversation.
Both call to the same table, but we will tell Eloquent to use different columns as primary key.
So to get a Conversation model, I added a distinct() to my newQuery function, and select only conv_id out since that's the only information about a conversation.
So in the end I have this:
models/Message.php
class Message extends Eloquent
{
protected $table = 'messages';
protected $primaryKey = 'id';
public $timestamps = false;
}
models/Conversation.php
class Conversation extends Eloquent
{
protected $table = 'messages';
protected $primaryKey = 'conv_id';
public $timestamps = false;
public function newQuery($excludeDeleted = true)
{
return parent::newQuery()
->select('conv_id')
->distinct();
}
public function messages()
{
return $this->hasMany('Message', 'conv_id')
->orderBy('id', 'desc')
->take(10);
}
}
Now I can do:
Conversation::with('messages')->whereIn('conv_id', [1, 2])->get();
which gives me a list of conversations with conv_id 1 and 2, along with the 10 latest messages for each of them.
This is the first time I do something like this. So I code-tested and it works.
Update: The code in my answer perform these two queries only (retrieved from DB::getQueryLog()):
select distinct `conv_id` from `messages` where `conv_id` in (?, ?);
select * from `messages` where `messages`.`conv_id` in (?, ?) order by `id` desc limit 10;

Categories