Avoid unuseful doble query laravel - laravel

I have a question for you!
I got my table with data structure like this:
acktime
temperature
2021-10-30 14:16:38
15.12
2021-10-30 14:20:12
15.14
a lot of data
in the same day
2021-10-31 10:16:38
13.16
2021-10-31 10:20:12
14.12
I have my model RawData and I create a collection instance with:
$mydata = rawData::select('acktime')->OrderBy('acktime', 'asc')->get();
Now I have to find how much days there's in and I found it in this way:
foreach ($mydat as $value) {
$ackday = Carbon::parse($value['acktime'])->format('Y-m-d');
if ($tempDay != $ackday){
Log::info('foun a new day! '.$ackday);
$daystoreport[] = $ackday;
$tempDay = $ackday;
}
}
I got my $daystoreport array with the days found in the db
Now I need to take handle $mydata day per day and for the moment I did it with:
$onedayData = rawData::whereDate('acktime', $daystoreport[0])
->get();
But this make me an unuseful query cause, I have already get all data from the table before ($mydata)...
Unfortunatly I can't do something like this:
for(i=0;i=length of the array; i++){
$onedayData = $mydata->whereDate('acktime', $daystoreport[i]);
..do some stuff
}
Any suggestions?

If I am not misunderstanding what you are trying to do, you could simply group your data by the day and then perform your actions on the respective collections:
RawData::all()
->groupBy(function (RawData $item) {
// Format the time to a day string without time and group your items by that day
return $item->acktime->format('Y-m-d');
})
->each(function (Collection $day) {
// Do day-based stuff in here
});
Optionally, you can also group your days already in the database query

Related

How to express Larvel subqueries to use within a whereIn

I'm sorry for asking somthing so simple, but I can't get the docs (https://laravel.com/docs/9.x/queries#subquery-where-clauses)
I'm writting something like a social network functionality, so I have my messages table and my users table, there's another pivot table where I store the users the user follows, and they work pretty well.
I want to represent the following SQL in Laravel's ORM
SELECT * FROM mensajes
WHERE user_id=1
OR user_id IN (SELECT seguido_id FROM seguidos WHERE user_id=1)
The idea is I'll get the user's posts, and also the posts from the users that the user follows.
My following solution works, but I feel it's quite dirty, and should be solved with a Subquery
// this relation returns the users the user is following, ans works correctly
$seguidos = auth()->user()->seguidos;
// I store in an array the ids of the followed users
$seg = [];
foreach ($seguidos as $s) {
array_push($seg, $s->id);
}
array_push($seg, auth()->user()->id);
// Then I retrieve all the messages from the users ids (including self user)
$this->mensajes = Mensaje::whereIn('user_id', $seg)
->orderBy('created_at', 'desc')
->get();
I'd like to change everything to use subqueries, but I don't get it
$this->mensajes = Mensaje::where('user_id', auth()->user()->id)
->orWhereIn('user_id', function($query) {
// ... what goes here?
// $query = auth()->user()->seguidos->select('id');
// ???? This doesn't work, of course
}
->orderBy('created_at', 'desc')
->get();
You can simply construct the raw query as you have done with the SQL.
->orWhereIn('user_id', function($query) {
$query->select('seguido_id')
->from('seguidos')
->where('seguidos.user_id', auth()->user()->id);
});
But normally sub queries have a relationship between the primary SQL query with the sub query, you don't have this here and instead of doing one query with a sub query, you can quite simply write it as two queries. If you are not calling this multiple times in a single request, one vs two queries is insignificant performance optimization in my opinion.
Mensaje::where('user_id', auth()->user()->id)
->orWhereIn(
'user_id',
Seguidos::where('user_id', auth()->user()->id)->pluck('seguido_id'),
);

Laravel, Eloquent and Kyslik sorting joined tables?

I'm stuck again. This time with the sorting links for 3 joined tables.
I'm building my query like this:
...
$query= Interaction::where('interactions.user_id', $id)->where('interactions.active', '1');
$query->join('contacts','interactions.contact_id','=','contacts.id');
$query->join('products','interactions.product_id','=','products.id');
$contact_username = request('contact_username');
$contact_name = request('contact_name');
$contact_type = request('contact_type');
$product_name = request('product_name');
$platform = request('platform');
$dateMode = request('dateMode');
$date = request('date');
$completed_at = request('completed_at');
$notesMode = request('notesMode');
$notes = request('notes');
//settings up some filters based on the request:
if ($contact_username) $query->whereJsonContains('contacts.username', $contact_username);
if ($contact_name) $query->where('contacts.name', $contact_name);
if ($contact_type) $query->where('contacts.type', $contact_type);
if ($product_name) $query->where('products.name', $product_name);
if ($platform) $query->where('interactions.platform', $platform);
if ($notes && $notesMode=='+') $query->where('interactions.notes', 'like', "%".$notes."%");
if ($notes && $notesMode=='-') $query->where('interactions.notes', 'not like', "%".$notes."%");
if ($date && $dateMode) $query->whereDate('date', $dateMode, $date);
if ($completed_at && $dateMode) $query->whereDate('completed_at', $dateMode, $completed_at);
//and finally:
$interactions = $query->sortable()->paginate(20,['interactions.*','contacts.username','contacts.name','contacts.type','products.name']);
...
Then in my view I have:
<th>#sortablelink('contact.username','Username')</th>
<th>Platform</th>
<th>Type</th>
<th>#sortablelink('contact.name','Name')</th>
<th>#sortablelink('product.name','Product')</th>
<th>#sortablelink('date','Date')</th>
<th>#sortablelink('notes','Notes')</th>
The last 2 links work great and everything is sorted as it should be, because they are from the "Interactions" table, but the links that should sort by the columns from the joined "Products" and "Contacts" tables fail.
What is the right way to reference these columns in the #sortablelink when they are coming from the joined tables?
PS: I have two other views which are working fine when only the "Products" and "Contacts" tables are used and the sorting for each column in these tables works fine, so I know the Models are set up correctly.
The solution turned out to be quite simple, but I had to sleep on the problem and tackle it again in the morning. :)
Instead of joining the two other tables manually, I did it like this:
$query= Interaction::where('interactions.user_id', $id)->where('interactions.active', '1')->with(['contact','product']);
//the two JOIN rows that were here were dropped. Everything else stayed the same.
and I added the relations in my "Interaction" model like this:
public function product() { return $this->belongsTo(Product::class); }
public function contact() { return $this->belongsTo(Contact::class); }
I hope this will be helpful to somebody. I lost a lot of hours trying to figure it out last night.

Laravel Return Grouped Many To Many Relations

How do you return a collection of a grouped dataset in a ManytoMany relationship with this scenario?
Here is a sample of what dataset I want to return
So let's take the favorites as the genres and the highlighted date is the genres name, it's also a collection as well. I want to group it based on the genres name in that collection.
My model:
Video
```
public function genres() {
return $this->belongsToMany(Genre::class);
}
```
Genre
```
public function videos() {
return $this->belongsToMany(Video::class);
}
```
I tried the following already but can't seem to get it.
```
$videos = Video::with('genres')->all();
$collection = $videos->groupBy('genres.name');
```
I want to group the dataset by the genres name knowing the genre's relationship is also a collection of genres.
Try something like:
Video::with('genres')->get()->groupBy('genres.*.name');
Or:
$videos = Video::with('genres')->all();
$collection = $videos->groupBy('genres.*.name');
Note that above is code you posted, just after replacing "genres.name" with "genres.*.name".
Just noticed the post is old, this at least works on latest Laravel.
Collections and query builders share many similar functions such as where() groupBy() and so on. It's nice syntax sugar, but it really does obscure the underlying tech.
If you call $model->videos... like a property, that's a collection (query has executed).
If you call $model->videos()... like a method, that's a query builder.
So if you want to get the job done in sql, you can do something like...
$video_query_builder = Video::with('genere');
$video_query_builder->groupBy('genere_id');
$result = $video_query_builder->get();
You can chain it all together nice and neatly as was suggested in the comments... like this:
$result = Video::with('genere')
->groupBy('genere_id')
->get();

How can I minimize the amount of queries fired?

I'm trying to create a tag cloud in Laravel 4.1, tags can belong to many posts, and posts can have many tags. I'm using a pivot table to achieve this (post_tag).
So far I've come up with this to fetch the tags and check how many times it's used:
public static function tagCloud($tags, $max = 10) {
foreach($tags->toArray() as $tag) {
$count = DB::table('post_tag')
->where('tag_id', $tag['id'])
->count();
$cloud[$tag['slug']] = $count;
}
sd($cloud);
}
I pass Tag::all() to the above function. Obviously that's going to fire a crazy amount of queries on the database, which is what I'm trying to avoid. Normally you'd use eager loading to fix this problem, but it seems the documentation does not mention anything about eager loading pivot tables in combination with aggregates.
I hope someone can shine a light on how to optimize this current function or can point me in the right direction of a better alternative.
Sometimes it's just hard reduce them, but Laravel Cache is your friend:
$users = DB::table('users')->remember(10)->get();
It will remember your query for 10 minutes.
I believe you have a many-to-many relationship with posts in your tags table like this:
public function posts()
{
return $this->belongsToMany('Post');
}
So, you are able to do something like this:
$tags = Tag::with('posts')->get();
Then you may loop though the tags to find out how many posts each tag contains, like this:
foreach($tags as $tag) {
$tag->posts->count();
};
So, you may write your function like this:
public function scopeTagCloude($query) {
$cloud = [];
$query->with('posts')->get()->each(function($tag) use (&$cloud) {
$cloud[$tag['slug']] = $tag->posts->count();
});
return $cloud;
}
You may call this function like this:
$tagCloude = Tag::tagCloude();
If you dd($tagCloude) then you'll get something like this (example is taken from my blog app):
array (size=4)
'general' => int 4
'special' => int 5
'ordinary' => int 5
'extra_ordinary' => int 2

Decrease count of detached tags

I have Post and Tag models
For adding tag to post I have:
public function addTag($tag) // $tag is string
{
$slug = Str::slug($tag);
$t = Tag::where("url", $slug)->first();
if(!$this->in_array_field($slug, 'name', $this->tags))
{
if(!isset($t))
{
$t = new Tag;
$t->name = $tag;
$t->url = $slug;
$t->count = 1;
}
else
{
$t->increment('count');
}
$this->tags()->save($t);
}
return $t->id;
}
After adding all tags I invoke sync to remove tags which are not any more in set
$this->tags()->sync($tagsIds); // $this is Post model
Everything is working, but how to decrease detached tags count?
Is there any handler or shoud I merge arrays and compare if not in old set - attach and increase, not in new set - detach and decrease or completly another way.
In my personal blog, I use sql to get the count (I have a relatively low number of tags so it has a low over-head + plus I cache the result):
// Get list of tags, ordered by popularity (number of times used)
return \DB::table('tags_articles')->select('name', 'url_name', 'tag_id', \DB::raw('count(`tag_id`) as `tag_count`'))
->join('tags', 'tags.id', '=', 'tags_articles.tag_id')
->groupBy('tag_id')
->orderBy('tag_count', 'DESC')
->take($limit)
->get();
Alternatively, you may want to run a separate query to update each tag count separate from that process - either in a cron, or as a new query run after sync() is called. Assuming you hav a low amount of writes (aren't often tagging items many times per second), either way probably won't cause too many bottlenecks.
Lastly, there are "events" fired after database updates. See if some of these model events can be used to update the count after an insert ('saved') to your Tag model.

Resources