Merge and Sort two Eloquent Collections? - php

I've two Collections and I want merge it to one variable (of course, with ordering by one collumn - created_at). How Can I do that?
My Controllers looks that:
$replies = Ticket::with('replies', 'replies.user')->find($id);
$logs = DB::table('logs_ticket')
->join('users', 'users.id', '=', 'mod_id')
->where('ticket_id', '=', $id)
->select('users.username', 'logs_ticket.created_at', 'action')
->get();
My Output looks for example:
Replies:
ID | ticket_id | username | message | created_at
1 | 1 | somebody | asdfghj | 2014-04-12 12:12:12
2 | 1 | somebody | qwertyi | 2014-04-14 12:11:10
Logs:
ID | ticket_id | username | action | created_at
1 | 1 | somebody | close | 2014-04-13 12:12:14
2 | 1 | somebody | open | 2014-04-14 14:15:10
And I want something like this:
ticket_id | table | username | message | created_at
1 |replies| somebody | asdfghj | 2014-04-12 12:12:12
1 | logs | somebody | close | 2014-04-13 12:12:14
1 | logs | somebody | open | 2014-04-14 11:15:10
1 |replies| somebody | qwertyi | 2014-04-14 12:11:10
EDIT:
My Ticket Model looks that:
<?php
class Ticket extends Eloquent {
protected $table = 'tickets';
public function replies() {
return $this->hasMany('TicketReply')->orderBy('ticketreplies.created_at', 'desc');
}
public function user()
{
return $this->belongsTo('User');
}
}
?>

A little workaround for your problem.
$posts = collect(Post::onlyTrashed()->get());
$comments = collect(Comment::onlyTrashed()->get());
$trash = $posts->merge($comments)->sortByDesc('deleted_at');
This way you can just merge them, even when there are duplicate ID's.

You're not going to be able to get exactly what you want easily.
In general, merging should be easy with a $collection->merge($otherCollection);, and sort with $collection->sort();. However, the merge won't work the way you want it to due to not having unique IDs, and the 'table' column that you want, you'll have to make happen manually.
Also they are actually both going to be collections of different types I think (the one being based on an Eloquent\Model will be Eloquent\Collection, and the other being a standard Collection), which may cause its own issues. As such, I'd suggest using DB::table() for both, and augmenting your results with columns you can control.
As for the code to achieve that, I'm not sure as I don't do a lot of low-level DB work in Laravel, so don't know the best way to create the queries. Either way, just because it's looking like starting to be a pain to manage this with two queries and some PHP merging, I'd suggest doing it all in one DB query. It'll actually look neater and arguably be more maintainable:
The SQL you'll need is something like this:
SELECT * FROM
(
SELECT
`r`.`ticket_id`,
'replies' AS `table`,
`u`.`username`,
`r`.`message`,
`r`.`created_at`
FROM `replies` AS `r`
LEFT JOIN `users` AS `u`
ON `r`.`user_id` = `u`.`id`
WHERE `r`.`ticket_id` = ?
) UNION (
SELECT
`l`.`ticket_id`,
'logs' AS `table`,
`u`.`username`,
`l`.`action` AS `message`,
`l`.`created_at`
FROM `logs` AS `l`
LEFT JOIN `users` AS `u`
ON `l`.`user_id` = `u`.`id`
WHERE `l`.ticket_id` = ?
)
ORDER BY `created_at` DESC
It's pretty self-explanatory: do the two queries, returning the same columns, UNION them and then sort that result set in MySQL. Hopefully it (or something similar, as I've had to guess your database structure) will work for you.
As for translating that into a Laravel DB::-style query, I guess that's up to you.

Related

How to sum a colum from a related model efficiently on Laravel

Ok I got this table
affiliates_referral_clicks
id | affiliate_id | clicks | date
1 | 1 | 10 | 2021-07-14
2 | 1 | 2 | 2021-07-11
3 | 2 | 1 | 2021-07-11
4 | 2 | 14 | 2021-07-10
...
Of course my Model Affiliate has a relationship with referralClicks
Affiliate.php
public function referralClicks(){
return $this->hasMany(AffiliateReferralClick::class,'affiliate_id');
}
Now I want to bring all Affiliates with the SUM of all their clicks that have a date between a given date. I implemented it like this
$affiliate = Affiliate::with(['referralClicks' => function($query) use($params) {
$query->whereDate('date','>=', $params['dateFrom'])
->whereDate('date','<=', $params['dateTo'])
->select('clicks')
;
}])->get();
foreach ($affiliates as $affiliate){
$affiliate->totalClicks = $affiliate->referralClicks->sum('clicks');
}
this works fine, but since the affiliates_referral_clicks table is waaaay too big and the request ends up being too slow, I think if you do the query without using Eloquent's helpers you can get a much faster query.
So my question would be...how can I do the same I just did but with raw querys (or whatever the most efficient way is)? Im using a MySQL DB I hope you guys can help me!
Haven't tried that yet but that's how I'd solve this (if we assume, you only need the sum and nothing else from the relationship):
$affiliate = Affiliate::withSum(['referralClicks.clicks as totalClicks' => function($query) use($params) {
$query->whereDate('date','>=', $params['dateFrom'])
->whereDate('date','<=', $params['dateTo'])
->select('clicks')
;
}])->get();

Laravel Relation where not in

I have following table
users
id | username | password
1 | scott | 98746
2 | mark | 6542
3 | michel | 6589
user_detail
id | user_id | status | mobile_number
1 | 1 | pending | 987643210
2 | 2 | review | 3216547901
Now i want to retrieve those record where user has no records in user_detail table where status=pending
I have tried using relations in latest version
$user=User::with('userDetail')
->whereDoesntHave('userDetail',function ($query){
$query->where('status','pending');
})->get();
Same logic i am looking for without relations in laravel.Since we are using old laravel version which doesn't support.
I do not know what you mean by there are no relations, but if you have to do it with plain SQL, the query will look something like this:
$qry = "SELECT * FROM users WHERE id not in (SELECT u.id FROM users u INNER JOIN user_details d ON (u.id = d.user_id AND d.status = 'pending'));"
Then you can run the query by calling:
$results = DB::select($qry);
PS: Since I was not able to find it in the old docs, DB::select() may require 2nd param. If that is the case, just pass null as the second parameter.
EDIT:
I am not sure if this will work, and since it is for an old version, I am unable to test it, but something similar should work:
$rest = User::whereNotIn('id', function($q){
$q->select('user_id')->from('user_detail')->where('status','pending');
});
I believe you can do this by using left joins, something like this:
$rest = DB::table('users')
->leftJoin('userDetail','users.id','=','userDetail.user_id')
->whereNull('userDetail.id')
->orWhere('userDetail.status','=','pending')
->get();
If you need the eloquent collection of Users, you can use the hydrate method like this:
$rest = User::hydrate(DB::table('users')
->leftJoin('userDetail','users.id','=','userDetail.user_id')
->whereNull('userDetail.id')
->orWhere('userDetail.status','=','pending')
->select('users.*')
->get()->toArray());
cheers!

Laravel 5.2 trouble with SQL query in eloquent

I am having trouble with eloquent in Laravel 5.2
What I want to achieve is (hopefully) a query which fulfills these steps:
updated_at >= value1
updated_at <= value2
event = value3
groupBy = value4
count entries of each "group" (each grouped By group)
Output: latest updated_at for each group (max(updated_at)) and the count value for each group
What I have so far is:
Table 'logs' overview:
---------------------------
| id | increment |
-----------------------------
| key | varchar(255) |
-----------------------------
| video_id | varchar(255) |
-----------------------------
| event | varchar(255) |
-----------------------------
| created_at | timestamp |
-----------------------------
| updated_at | timestamp |
-----------------------------
Query in Laravel:
$showed = self::where('updated_at','>=',$value1)->where('updated_at','<=',$value2)->where('event',$value3)->groupBy($avlue4)->get();
What I am missing are step 5 and the resulting output. And I have no idea how I could include this to the Laravel eloquent.
Question: Does anyone have a concrete idea of how to achieve this query? If it's not possible, what are the steps to take?
You may pass an array of conditions like this:
$showed = DB::table('tableNameHere')->where([
['updated_at', '>=', $month_start],
['updated_at', '<=', $month_end],
['event', '=', 'showed'],
])->groupBy('id')->get();
To count the entries you can then try it this way:
$results = $showed->count();
Couldn't test the code. Let me know if this worked.
If someone also has troubles with building this kind of eloquent: My solution was to group the query by ID, so the entries in Array would be something like:
[0] => [Object, Object],
[1] => [Object]
My query:
$temp = self::where('updated_at','>=',$month_start)
->where('updated_at','<=',$month_end)
->where('event','completed')
->orWhere('event', 'showed')->get();
$result = $temp->groupBy('id')->toArray();;

Using nested queries and many to many relationships in Doctrine's QueryBuilder

So I'm having a bit of trouble thinking of how to approach this using a query builder. Currently, I have three objects that are the following:
HelpRequest
id
...
status
Filter
id
name
statuses -> ManyToMany(targetEntity="Status")
Status
id
name
A filter can have multiple statuses so there is a table that is keeping track what statuses are part of a specific filter.
Sample Data
help_requests
---
| id | content | status |
| 1 | hello | 3 |
filters
---
| id | name |
| 1 | Active |
| 1 | Inactive |
statuses
---
| id | name |
| 1 | Open |
| 2 | Closed |
| 3 | Waiting User Response |
status_filter
---
| status_id | filter_id |
| 1 | 1 |
| 3 | 1 |
| 2 | 2 |
The status_filter table is automatically generated from a ManyToMany relationship in doctrine between a Status object and a Filter object.
Based on the given information, I've written this SQL query but now I'm having troubles writing this with QueryBuilder.
SELECT * FROM help_requests WHERE status IN (SELECT status_id FROM status_filter WHERE filter_id = 1)
If there's any more information I can give, let me know. I've read multiple questions on SO and have tried a number of things but I can't seem to get it right. I'm aware I could just hard coded that query but I'd like the experience using QueryBuilder
Thanks for the help!
Update
In the end, since I couldn't get it to work with QueryBuilder and I didn't want to create a new entity solely to map two other entities together, I decided to use createQuery() instead and this is what I came up with:
SELECT
hr
FROM
HelpRequest hr
WHERE
hr.status
IN (
SELECT
s.id
FROM
Filter f
JOIN
f.statuses s
WHERE
f.name = :name
)
Thank you everyone for the help.
Try this query, and put is in your HelpRequestsRepository class:
$subquery = $this->->select('st.status_id')
->from('/path/to/StatusFilter', 'st')
->where('st.filter_id = 1');
$query = $this->createQueryBuilder('hr')
->select('*')
->where('hr.status IN (' . $subquery->getDQL() . ')')
->getQuery();
Try this approach in the HelpRequestsRepository class:
$qb = $this->createQueryBuilder('hr');
$qb->select("hr");
$qb->join("::Status","s",Expr\Join::INNER_JOIN, "hr.status=s" );
$qb->join("::Filter","f",Expr\Join::INNER_JOIN, "s.filters=f" );
$qb->where("f.name = :name");
$qb->setParameter('name', $nameOfTheFilterToBeFound)
Hope this help

Exploding in php

In my table 'users' there are 'friends' ,
Like this :
+----+------+---------+
| id | name | friends |
+----+------+---------+
| 1 | a | 0,1,2 |
| 2 | b | 0,1,3 |
| 3 | c | 0,1 |
+----+------+---------+
How do I use the explode function to get the friends id one by one (not 0,1,2) that are separated by a comma (,) ;
How do I select the id? (Example) :
$sql = Select id from users where id = (exploded)
if (mysql_num_rows($sql) > 0 ) {
$TPL->addbutton('Unfriend');
}else{
$TPL->addbutton('Add as Friend')
}
The solution here is actually a slight change in your database structure. I recommend you create a "many-to-many" relational table containing all of the users friends referenced by user.
+---------+-----------+
| user_id | firend_id |
+---------+-----------+
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 2 | 1 |
| 2 | 5 |
+---------+-----------+
If you are storing lists of values within one field then that is the first sign that your database design is not quite optimal. If you need to search for a numerical value, it'll always be better to place an index on that field to increase efficiency and make the database work for you and not the other way around :)
Then to find out if a user is a friend of someone, you'll query this table -
SELECT * FROM users_friends WHERE
`user_id` = CURRENT_USER AND `friend_id` = OTHER_USER
To get all the friends of a certain user you would do this -
SELECT * FROM users_friends WHERE `user_id` = CURRENT_USER
Just a simple example that will make you clear how to proceed:
// Obtain an array of single values from data like "1,2,3"...
$friends = explode(',', $row['friends']);
Then, back in your query:
// Obtain back data like "1,2,3" from an array of single values...
$frieldslist = implode(',', $friends);
$sql = "SELECT * FROM users WHERE id IN ('" . $frieldslist . "')";
to get an array of if ids from your string explode would be used like this
$my_array = explode("," , $friends);
but you'd probably be better using the mysql IN clause
$sql = "Select id from users where id in (".$row['friends'].")";
Just a quick idea. Change your database's table. It is certain that after a while many problems will arise.
You could have something like this.
id hasfriend
1 2
1 3
2 1 no need to be here (You have this already)
2 4
.....
You can do this by using indexes for uniqueness or programming. You may think of something better. Change your approach to the problem to something like this.

Categories