Cant retrieve record from large scale table row - php

I am using laravel framework.In my database records table have more then 400000 row. Now I want to retrieve record using some condition
logic :
need to match service id
need to match status
but my application cant retrieve data (unable to handle this request).
I am using query using this
foreach ($ven as $ven){
$data = $ven->data;
$record = $data ->records()->where('status','success')->get();
}
My status column already added in index.
Need suggestion

First you need to keep statuses in int form not as strings. It would help you in filtering records easily.
And use chunk for handling large datasets. I suppose $ven is for venue. So, follow the code below.
DB::table('venues')
->select('venues.*', 'records.*')
->join('records', 'venues.id', '=', 'records.venue_id')
->orderBy('id')->chunk(1000, function($venues) {
foreach ($venues as $venue) {
// your logic
}
});
Note: I used query builder instead eloquent. That is because query builder is faster than eloquent.

The way you are trying to access related records in loop could create N + 1 problem
As per official docs When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem
You could eager load your related data as
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->get();
For eager loading only 2 queries will be executed to get your data for main model and related model. Each row in $results will a collection of related records which you can iterate to list details of each related object.
To get filtered rows of related model you could modify with() method as
$results = ParentModel::with(['records' => function ($query) {
$query->where('status','=','success');
}])
->where(...) /* If there are any filters for ParentModel */
->get();
To get filtered rows of ParentModel on basis of related records you could use whereHas
$results = ParentModel::with('records')
->where(...) /* If there are any filters for ParentModel */
->whereHas('records', function ($query) {
$query->where('status','=','success');
})->get();

Related

Laravel Where Clause To Search In Columns Which Are Not In The Database

I have a table organisations with a number of columns, namely id, name, created_at and updated_at.
In my Organisation model, I added a custom attribute by adding the following code in the model:
// Add custom attributes
protected $appends = [
'device_count',
];
and:
public function getDeviceCountAttribute()
{
// Count organisation's devices
$device_count = Device::where('organisation_id', '=', $this->id)->count();
return $device_count;
}
In my controller, I am trying to search (using the where clause) by the device_count attribute, but I'm getting an error since this is not a real column in my database table.
This is how I'm searching:
$organisations = Organisation::query();
$organisations = $organisations->where('device_count', '=', 0);
$organisations = $organisations->get();
This is the error:
[2020-10-14 12:29:27] local.ERROR: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'device_count' in 'field list'...
Is there an alternative to the where clause in order to search by device_count please?
You can't query against attributes like that, they only exist on the model and do not exist in the database. However, you don't need to create a getter for that.
Laravel has a withCount() method you can use instead of that attribute - which is the correct approach of obtaining relational counts.
So a query with that attribute would look like
$organisations = Organisation::withCount('devices')->get();
The resulting models will now have a device_count attribute on them, that you can use in your views.
Then in your query, to filter those records that has at least a certain relational count, you can do
$organisations = Organisation::has('devices', '=', 0)->get();
Since you're looking for those that do not have any relation, you can replace it with doesntHave(). The above snippet is included to show how you can query against a specific set of relational counts.
To query only those that don't have any related devices, simply do
$organisations = Organisation::doesntHave('devices')->get();
This assumes that you have defined a devices relation on your Organisation class.
public function devices() {
return $this->hasMany(App\Models\Device::class); // Or App\Device::class if your models are not in the Models namespace/directory
}
The approaches above are the Laravel-way of doing it - and you should therefor remove your getDeviceCountAttribute() method and the corresponding device_count from $appends.
As per your requirement you want to get those Organisations which doesnot have any devices
For that, you need to use doesntHave method.
Make a relationship in the Organisation model.
public function devices() {
return $this->hasMany('App\Models\Device','organisation_id','id');
}
Now call the mothed in controller/
$organisations = Organisation::doesntHave('devices')->get();
it will return you those organisations which devices are not exists.

Laravel Eloquent query unexpected result

I've found some query result really unexpected.
It's Laravel 5.2
We have following entity:
User with method:
public function roles() : BelongsToMany
{
return $this->belongsToMany(Role::class)->withPivot('timestamp');
}
Each User can have many roles, so we have also Role entity (but it doesn't matter much in my question) and pivot table user_role with timestamp field (and ids of course), because we hold information about time, when User achieved specific role.
I want to get all Users with theirs last assigned Role
When I create query (in User context in some repository):
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc');
}])->all();
the result will contain Users with Roles entities inside itself ordered by timestamp - it's ok. But I want to retrieve only one last role inside each User entity not all ordered.
So...
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc')->limit(1);
}])->all();
And then I retrieve Users but only User which achieved some Role for the very last time contains it! All the other Users have their roles field containing empty array.
Why ordering was performed on each Users relation separately, but when I added limit it behaved like a global limit for all.
It drives me crazy...
Thanks for advices.
EDIT
I've created lastRoles() method to get all Roles ordered desc. But all, retrieving one is impossible.
public function lastRoles() : BelongsToMany
{
return $this->BelongsToMany(Roles::class)->withPivot('timestamp')->latest('timestamp');
}
And for testing:
$users = (new User())->with('lastRoles')->get();
But now I must iterate over Users and invoke lastRoles() on each one:
foreach ($users as $user) {
var_dump($user->lastRoles()->get()->first()->name);
}
Then I retrieve names of latest Roles assigned to each User.
So... There is no way to do it in one query? This is the only way?
For this to work, you would need a helper function:
public function latestRole()
{
return $this->hasOne(Role::class)->withPivot('timestamp')->orderBy('timestamp', 'DESC');
}
And then:
$this->with('latestRole')->get();
Credits to this awesome article.
When you eager load a relationship with query constraint(s), the query will be run once to load all relationships, not each one individually. This is the expected behavior. Think about it, eager loading exists to turn many queries into one query in order to optimize performance. There is only one query executed, so your limit constraint will limit the entire result set, rather than on a per model basis.
To circumvent this, you could try creating another belongsToMany method that adds the desired limit constraint. The following code is untested:
public function lastRole() : BelongstoMany
{
return $this->belongsToMany(Role::class)
->withPivot('timestamp')
->orderBy('timestamp', 'desc')
->limit(1);
}
Assuming this works, you can then simply change the relationship method from roles to lastRole and remove your query constraint:
$this->with('lastRole')->all();

Push an array into Laravel collection with matching keys

I have a collection called User, I also have an array containing two models Post relating to the User model.
The User collection contains a primary key id and each model in my Post collection I have a foreign key user_id.
I am currently executing the following:
foreach ($users as $user) {
foreach ($posts as $post) {
if ($post->user_id == $user->id) {
$user->posts->push($post);
}
}
}
This somewhat works, but not entirely because it pulls in all related posts instead of the recent two posts a user has made.
The array looks like the following:
My User schema looks like; with a hasMany relationship to Post:
You can load the posts associated to a User using with, something like
$user = User::with('posts')->find($id);
But your scenario sounds specifically collecting the latest two Post belonging to a User. To limit your results you can also use scopes.
Something like the following on your Post model would work:
public function scopeLatest($query, $latest = 2)
{
return $query->limit($latest);
}
Then collect these by:
// The user record.
$user = User::find($id);
// Latest 2 posts for this user.
$posts = $user->posts()->latest();
// Latest 5 posts for this user.
$posts = $user->posts()->latest(5);
However, should you with to load the latest 2 posts with the user in a single query - then you could make a new relation:
public function latestPosts()
{
return $this->hasMany(Post::class, 'post_id', 'id')
->orderBy('created_at', 'ASC')
->limit(2);
}
This would work in the following way:
// Load the user with the latest 2 posts.
$user = User::with('latestPosts')->find($userId);
// Access these using; this will be a Collection containing 2 `Post` records.
dd($user->latestPosts);
Basically with Eloquent, when you call $this->latestPosts Eloquent will run latestPosts() and hydrate the related records. Using with this hydration occurs with a single query and the relations are already defined.
The difference between the method latestPosts() and the property $latestPosts is simple.
The method will always return a specific Relation Collection allowing you to chain additional conditions;
So: $user->latestPosts()->get() is the same as $user->latestPosts.
You cannot use query constraints / eager loading to do this. Doing so will only work if you are retrieving the posts for one user. However, if you try to retrieve the posts for multiple users, it will fail because eager loading / query constraints will limit the related results as a whole. To understand, you have to look at the queries Eloquent generates. Lets take a look at an example where you only need one user's posts.
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->find(1);
In this example, we are getting a user with a primary key of 1. We also also retrieving his/her posts but limiting it so we only retrieve 2 posts. This works, and it will generate 2 queries similar to this:
select * from `users` where `users`.`id` = 1 limit 1
select * from `posts` where `posts`.`user_id` in (1) limit 2
Okay. Now, why doesn't this work if you try to get more than 1 user (or a collection of users)? For example:
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->get();
In this case, I changed find(1) to get(), and it will generate 2 queries like this:
select * from `users`
select * from `posts` where `posts`.`user_id` in (?, ?, ?, ... ?) limit 2
It's important to take a look at the second query. It's retrieving all the related posts, but at the end, you'll see that it has limit 2. In other words, it's limiting the entire related collection to only 2, which is why query constraints do not work for this.
Achieving this is actually pretty complex, but a fellow member (Jarek Tkaczyk) came up with a solution using MySQL variables, which you can find here: Laravel - Limit each child item efficiently
You can do this a bit simpler with https://laravel.com/docs/5.2/eloquent-relationships#eager-loading constraints.
Example: Users have many Dogs, but only take 2
$user = App\User::with(['dogs' => function ($query) {
$query->limit(2);
}])->find($user_id);
dump($user);
The anonymous constraining function would also have an orderBy in your case

Filtering Eloquent ORM query by relation

I need to filter my Eloquent query by relation. I have following relations:
User **has many** achievements
Game **has many** achievements
Now I need to filter achievements of user A to those gained in game B. This can be done this way:
$user->achievements()->whereGameId($game->id)
This is fine, but can I use $game object directly instead of filtering by ugly ID column?
Injecting $game object into a closure method and doing querying while eager loading is a way better approach in my opinion.
whereHas and orWhereHas is for limiting the result by querying relations.
$game = Game::find($gameID);
//magic happens here
$users = User::whereHas('achievements', function($query) use($game){
$query->where('gameId', $game->id);
})
->with('achievments') //if you want to have them inside collection
->get();
This limits both User (main collection result) along with achievments (relation).
Here's a detailed information (Querying Relations).
If you just want to filter achievments, but not User collection, you just need a closure function on eager loading:
$game = Game::find($gameID);
//magic happens here
$users = User::with(
array('achievements' => function($query) use($game){
$query->where('gameId', $game->id);
}))
->get();
This just limits achievments relation, but does not affect User (main collection)
Here's a detailed description (Eager Loading Constains)
edit:
So here's the PR https://github.com/laravel/framework/pull/4267 that lets you do this:
// 'game' being relation on the Achievement model
$user->achievements()->hasIn('game', $game);
// or equivalent dynamic call
$user->achievements()->hasInGame($game);
// of course you may use it like any other Builder method, for example:
User::hasInAchievements($achievement)->get();
No straightforward way to do this, unless the Game is related somehow to the User.
To be clear, say User and Game is related, then you can access achievements on the games Collection injecting Game model instead of 'ugly' $game->id:
$user->games->find($game)->achievements;

Querying on related models using Laravel 4 and Eloquent

Using Laravel 4 I have the following models and relations: Event which hasMany Record which hasMany Item. What I would like to do is something like this
Item::where('events.event_type_id', 2)->paginate(50);
This of cause doesn't work as Eloquent doesn't JOIN the models together when retrieving the records. So how do I go about this without just writing the SQL myself (which I would like to avoid as I want to use pagination).
What you want is eager loading.
It works like this if you want to specify additional constraints:
Item::with(array('events' => function($query) {
return $query->where('event_type_id', 2);
}))->paginate(50);
There is a pull request pending here https://github.com/laravel/framework/pull/1951.
This will allow you to use a constraint on the has() method, something like this:
$results = Foo::has(array('bars' => function($query)
{
$query->where('title', 'LIKE', '%baz%');
}))
->with('bars')
->get();
The idea being you only return Foos that have related Bars that contain the string 'baz' in its title column.
It's also discussed here: https://github.com/laravel/framework/issues/1166. Hopefully it will be merged in soon. Works fine for me when I update my local copy of the Builder class with the updated code in the pull request.

Categories