Laravel Model Collections with multiple tables and where statement - laravel

I need to get data from 2 tables using Laravel models and collections. I can do that with :
$results = Tasks::with('resource')->get();
how can i add a where statement to this? (I am trying to get all tasks where deleted=0;
//I tried $results = Tasks::with('resource')->get()->where('tasks.deleted', '!=','1');
with no luck

Since Tasks is the upper collection ( not nested ); you should be able to use this directly :
$results = Tasks::with('resource')->get()->where('deleted', '<>',1);
// all tasks where deleted is equal to 0
$results = Tasks::with('resource')->get()->where('deleted',0);

Related

How to get min value of specific column from belongsToMany relationship in eloquent

I have many-to-many relationship between projects-devices tables. And I want to get min value of a specific column (battery level) from devices table for each project.
I can success this with only one sql command. But how can I do that with an effective eloquent? Thank you.
1st table: projects
-- id
-- name
-- .
-- .
-- .
2nd table: devices
-- id
-- battery_level
-- .
-- .
-- .
3rd pivot table: device_project
-- device_id
-- project_id
Tables link is here
Query result which I wanna get is here
With raw sql:
This works well as I want, but I wanna do this with eloquent.
$projects = DB::select( DB::raw("select
`projects`.`id`, `projects`.`name`,
(select
`battery_level`
from `devices`
inner join `device_project`
on `devices`.`id` = `device_project`.`device_id`
where `projects`.`id` = `device_project`.`project_id`
order by `battery_level`
limit 1
) as `lowest_battery_level`
from `projects`"));
foreach ($projects as $project) {
$values[] = $project->lowest_battery_level
}
With eloquent:
The problem on this: it will send two separate sql queries although I can do that with only 1 query by using raw sql. Also it gets all devices' battery levels from database. But I need only the lowest one for each project.
$projects = Project::with('devices:battery_level')->get();
foreach ($projects as $project) {
$values[] = $project->devices->min('battery_level')
}
I have found my answer after many try. Hope this can help others as well.
addSelect method helped me about this and my eloquent code is more effective now. This creates only one query and no detailed (unnecessary) information about devices as I wanted. It gives only the lowest battery level for each projects.
Eloquent code for this:
$projects = Project::select('projects.id', 'projects.name')
->addSelect([
'lowest_battery_level' => Device::select('battery_level')
->leftJoin('device_project', 'device_project.device_id', '=', 'devices.id')
->whereColumn('device_project.project_id', 'projects.id')
->orderBy('battery_level', 'asc') // no need asc if you wanna get lowest value first
->limit(1)
])
->get();
// can use like this
foreach ($projects as $project) {
$values[] = $project->lowest_battery_level
}
This creates sql query like this:
Creates only 1 query and get only projects results without other devices' details.
select
`projects`.`id`,
`projects`.`name`,
(
select
`battery_level`
from `devices`
inner join `device_project` on `devices`.`id` = `device_project`.`device_id`
where `projects`.`id` = `device_project`.`project_id`
order by `battery_level`
limit 1
) as `lowest_battery_level`
from `projects`
Performance Comparison with Laravel Debugbar
There are 100 projects and 1000 devices in database. And every project have relationship randomly with 0-50 of devices. Also different projects can have relationship with same devices (many-to-many relationship).
With previous eloquent code:
$projects = Project::with('devices:battery_level')->get();
foreach ($projects as $project) {
$values[] = $project->devices->min('battery_level')
}
As it can be seen below, it uses 18 MB RAM and took 539 ms.
Creates 2783 Device objects and 100 Project objects
With new eloquent code: (which I showed above)
As it can be seen below, it uses 10 MB RAM and took 432 ms.
Creates no Device objects and only 100 Project objects
in your project model define the relationship like this
public function devices()
{
return $this->belongToMany(Device::class);
}
and in your device model define the relationship like this
public function projects()
{
return $this->belongToMany(Project::class);
}
then get the projects in yur controller
$values = [];
$project = Project::find(1); //write your own custom code for gettign the projects
foreach($project->devices as $device)
$values[] = $device->battery_level;
dd($values);

Wrong total pagination with select distinct in Laravel 6

I use Laravel 6.12, I have this request :
$queryJob = DB::table('jobs as j')->join('job_translations as jt', 'j.id', 'jt.job_id')
->whereNull('j.deleted_at')
->whereNull('jt.deleted_at')
->select('j.id', 'j.short_name', 'j.status', DB::raw("case when j.short_name = '{$request->short_name}' then 0 else 1 end"))
->distinct();
$jobs = $queryJob->paginate($qtyItemsPerPage);
The results displays an error for the total :
The total = 3, but as you can see the data contains only 2 elements.
I read here that when using a distinct, I must be clear on which column the total must be calculated: distinct() with pagination() in laravel 5.2 not working
So I modified my query like that:
$jobs = $queryJob->paginate($qtyItemsPerPage, ['j.*']);
But without success, the total is still wrong.
Hoping that I don't misunderstand your DB and relations structure and purpose of your query perhaps this will avoid using distinct or groupBy altogether?
$shortname = $request->input('short_name');
$queryJob = Job::with('job_translations')->select('id','short_name',
'status', DB::raw("case when short_name = '" . $shortname . "'
then 0 else 1 end")
->paginate($qtyItemsPerPage);
Pagination can be easily manually added with skip and take in case you need to use groupBy
$queryJob->skip(($page - 1) * $qtyItemsPerPage)->take($qtyItemsPerPage)->get();
The solution for me was to pass a field name to the distinct() method.
With your example:
$queryJob = DB::table('jobs as j')
// joins, where and other chained methods go here
->distinct('j.id')
Solution taken from https://stackoverflow.com/a/69073801/3503615

Laravel Query Builder use multiple times

Is it possible to save a query bulider and use it multiple times?
for example, I have a model 'Tour'.
I create a long query buider and paginate it:
$tour = Tour::where(...)->orWhere(...)->orderBy(...)->paginate(10);
For example, 97 models qualify for the above query.
"Paginate" method outputs first 10 models qualifying for the query, but I also need to so some operations on all 97 models.
I don't want to 'repeat myself' writing this long query 2 times.
So I want something like:
$query = Tour::where(...)->orWhere(...)->orderBy(...);
$tour1 = $query->paginate(10);
$tour2 = $query->get();
Is that a correct way to do in Laravel? (my version is 5.4).
You need to use clone:
$query = Tour::where(...)->orWhere(...)->orderBy(...);
$query1 = clone $query;
$query2 = clone $query;
$tour1 = $query1->paginate(10);
$tour2 = $query2->get();
You can but it doesn't make any sense because every time a new query will be executed. So this code will work:
$query = Tour::where(...)->orWhere(...)->orderBy(...);
$tour1 = $query->paginate(10);
$tour2 = $query->get();
But if you want to execute just one query, you'll need to use collection methods for ordering, filtering and mapping the data. You'll also need to create Paginator instance manually:
$collection = Tour::where(...)->orWhere(...)->orderBy(...)->get();
$tour1 = // Make Paginator manually.
$tour2 = $collection;
$sortedByName = $collection->sortBy('name');
$activeTours = $collection->where('active', 1);

Eloquent to count with different status

It's possible to make one query to get total, sold & unsold in laravel eloquent?
$total_apple = Item::whereName('Apple')->count();
$sold_apple = Item::whereName('Apple')->whereStatus(2)->count();
$unsold_apple = Item::whereName('Apple')->whereStatus(1)->count();
Yes you can totally do that. You can use filter method on collection object returned by your Eloquent query.
$apples = Item::whereName('Apple')->get();
$soldApples = $apples->filter(function ($apple){
return $apple->status == 2;
});
$unsoldApples = $apples->filter(function ($apple){
return $apple->status == 1;
});
$soldApples and $unsoldApples contains the object of the items. You can then just use count($soldApples) and count($unsoldApples) to get their count.
filter method is against the collection object so there is no sql overhead.
There is no need run multiple queries or even fetch the entire results and use collection methods to loop through. Just use raw queries.
$apples = Item::whereName('Apple')
->selectRaw('COUNT(*) as total_apples,
SUM(status=2) as sold_apples,
SUM(status=1) as unsold_apples')
->first();
echo $apples->total_apples; // Outputs total apples
echo $apples->unsold_apples; // Outputs the unsold apples
echo $apples->sold_apples; // Outputs the sold apples
Since you are only doing simple counts though, you can use the query builder as well.
I would get all the items in one collection, then run the where statement on that collection. This should trigger a single Query.
$apples = Item::whereName('Apple')->get(); // This goes against SQL
$total_apple = $apples->count(); //This runs on the Collection object not SQL
$sold_apple = $apples->whereStatus(2)->count();
$unsold_apple = $apples->whereStatus(1)->count();

Get values from a doctrine collection with composite key

4 for on on my applications with Doctrine.
In there I'm using the following doctrine command to retrieve person object collection
//query
$people = $q->execute();
This return 20 objects. The primary key of the person object is a composite key with three attributes. Those are
id
department_id
name
I need to get person objects by searching in it as follows.
$id = 10;
$department_id = 1;
$name = "abc";
$people->get($id, $department_id, $name);
But this doesn't work and not give correct results. I tried with this and it gives null results which seems my collections primary key is not set.
$people->getKeyColumn();
I don't want to go through a foreach loop in collection and process it because when I deal with about 500 people, it slow down my application.
Can some one help me with this issue to get values from a doctrine collection.
Can you use something like this?
$people = Doctrine::getTable('Persons')
->createQuery()
->where('id = ? AND department_id = ? AND name = ?', array($id, $department_id, $name))
->execute();
It will get you a DoctrineCollection already filtered by the parameters provided.
'Persons' here is a Doctrine model name, not a table name from mySQL.
You can also use Doctrine's magic finders findBy*():
$people = Doctrine_Core::getTable('Persons')
->findByIdAndDepartmentIdAndName($id, $department_id, $name);

Resources