Eloquent query with a RAW order by - laravel

I have a eloquent query to get list items.
This is based on the idea that the sort column would be filled in, but in some circumstances it is not.
If its the case then I would like to order by the name of the list.
More or less what I wrote bellow.
order by CASE WHEN trickysort IS NOT NULL THEN trickysort::text ELSE list_name END
Here is the original query.
$list = Table_Lists::with(["listItems" =>
function($query)
{
$query
->orderBy('trickysort', 'asc')
->with("sublist.listItems");
}
])
->where("list_name","=", $name)
->where("hos_id","=", $hos_id)->get()->first();
return $list ? $list->toArray() : null;
The table structure is as follows.
Parent table -> table_lists
related child table -> table_list_items
the child table items are the one I need sorting. ( trickysort else list_name )
I hope this makes sense.
Im was thinking kind of a RAW query bit in the ->orderBy section of the child table.

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);

Laravel: How to get duplicated records and group them together?

The code below is what I have to get all the duplicated products (by title) and group them together. It works perfectly fine. However, I so many records in my Products table and getting all of them causes a performance issue. Is there a way this could be optimised to avoid getting all records and group them in one query? Thank you.
$products = Product::all();
$groupsOfProducts = $products->groupBy('title');
$duplicatedProductsGrouped = [];
foreach($groupsOfProducts as $productGroup) {
$productIsDuplicated = $productGroup->count() > 1;
if($productIsDuplicated) {
$duplicatedProductsGrouped[] = $productGroup;
}
}
var_dump($duplicatedProductsGrouped);
You can use having in the group by:
Product::groupBy('title')->having(DB::raw('count(*)'), ">", "1")->select('title')->get()
And you will get the titles of the duplicates, then you can query the database with those titles
EDIT:
Please also try and see if this is faster
Product::getQuery()->whereIn('title', array_column( DB::select('select title from products group by title having count(*) > 1'), 'title'))->get();
with this line you will get ONLY the products that has a duplicate title, and so your Collection groupby should be faster to aggregate the records by the title
Let your database do the work. When you call Product::all(), you're getting every single record, then making PHP do the rest. Change your query to something like the following:
Product::selectRaw("title, COUNT(*) AS count")->groupBy("title")->get();
The result will be a Collection of Product instances with a title and count attribute, which you can access and determine duplicated ones:
$products = Product::selectRaw("title, COUNT(*) AS count")->groupBy("title")->get();
$duplicatedProducts = collect([]);
foreach($products AS $product){
if($product->count > 1){
$duplicatedProducts->push($product);
}
}
dd($duplicatedProducts);

Using whereIn method to return multiple results for the same array value but at different index

$pidArray contains product ID's, some of those product ID's can be the same. I.E: 34 34 56 77 99 34. As is, it appears the whereIn method does not return results for a productId it has already found in $pidArray, even if it has a different index.
$productDataForOrder = Product::whereIn('id', $pidArray)->get(['id','price']);
$totalAmount = $productDataForOrder->sum('price');
$productDataForOrder now contains product data, but only for unique ProductID's in $pidarray. So when sum function is run, the sum is wrong as it does not take into account the price for multiple instances of the same productID.
The following code also does not return objects for every product ID in the array which are the same. So if $pidArray contains three identical product ID's, the query will only return a collection with one object, instead of three.
$query = Product::select();
foreach ($pidArray as $id)
{
$query->orWhere('id', '=', $id);
}
$productDataForOrder = $query->get(['id','price']);
$totalAmount = $productDataForOrder->sum('price');
You're not going to be able to get duplicate data the way that you're trying. SQL is returning the rows that match your where clause. It is not going to return duplicate rows just because your where clause has duplicate ids.
It may help to think of it this way:
select * from products where id in (1, 1)
is the same as
select * from products where (id = 1) or (id = 1)
There is only one record in the table that satisfies the condition, so that is all you're going to get.
You're going to have to do some extra processing in PHP to get your price. You can do something like:
// First, get the prices. Then, loop over the ids and total up the
// prices for each id.
// lists returns a Collection of key => value pairs.
// First parameter (price) is the value.
// Second parameter (id) is the key.
$prices = Product::whereIn('id', $pidArray)->lists('price', 'id');
// I used array_walk, but you could use a plain foreach instead.
// Or, if $pidArray is actually a Collection, you could use
// $pidArray->each(function ...)
$total = 0;
array_walk($pidArray, function($value) use (&$total, $prices) {
$total += $prices->get($value, 0);
});
echo $total;
The whereIn method only limits the results to the values in the given array. From the docs:
The whereIn method verifies that a given column's value is contained within the given array
Id make a query variable and loop through the array adding to the query variable in each pass. Something like this:
$query = Product::select();
foreach ($pidArray as $id)
{
$query->where('id', '=', $id);
}
$query->get(['id','price']);
Here is a code that would work for your use case expanding on #patricus
You first fetch an array of key as id and value as price from the products table
$prices = Product::whereIn('id', $pidArray)->lists('price', 'id');
$totalPrice = collect([$pidArray])->reduce(function($result, $id) use ($prices) {
return $result += $prices[$id];
}, 0);

How do I check whether a value in a query result is null or not? - Active Record - CodeIgniter

I wanted to know how to check whether there is a value present in a table (managers) and then add a 'yes' or 'no' string depending on if there is a value in that table or not.
$this->db->select('employees.first_name, employees.last_name, departments.department_name, departments.department_numb, titles.title');
$this->db->from('employees');
$this->db->where('first_name', $firstname);
$this->db->where('last_name', $lastname);
$this->db->join('department_manager', 'department_manager.emp_numb = employees.emp_numb', 'inner');
$this->db->join('departments', 'departments.department_numb = department_manager.department_numb', 'inner');
$this->db->join('titles', 'titles.emp_numb = employees.emp_numb', 'inner');
$this->db->where('department_name', $dept);
$this->db->where('title', $jobtitle);
$result = $this->db->get();
$data = array();
foreach($result->result() as $row)
{
$entry = array();
$entry['firstname'] = $row->first_name;
$entry['lastname'] = $row->last_name;
$entry['jobtitle'] = $row->title;
$entry['dept'] = $row->department_name;
$entry['deptid'] = $row->department_number;
//$entry['ismanager'] =
$data[] = $entry;
}
return $data;
I want to check whether an employee is present in the table 'department_manager' which is joined by an employees number. So if that employee number is not present in the table 'department_manager' then I want to insert in the array index $entry[ismanager'] a string which says 'no', and if the employee number is present in the table 'department_manager' then I want $entry['ismanager'] to hold the string 'yes'.
But I'm confused as to how to check that the employee is present or not in that table. Do I do it in the active record query or in the foreach loop? And if it is done in the foreach loop then how do I make that comparison as the query is completed?
Why have a field that is basically a calculated value? That's like having fields for quantity per box, quantity of boxes then saving the total items to a third field. Never save to the database something you can gain access to via a quick query. In your query above it's as simple as changing the dept manager join to a left join, including the dept manager id and saying if that field is blank in a record the person is not a manager. Using a LEFT join will return all records whether they have an entry in the management table or not.
Add: department_manager.emp_numb to the select.
The Join:
$this->db->join('department_manager', 'department_manager.emp_numb
= employees.emp_numb', 'left');
Then in the foreach:
if(!$row->department_manager.emp_numb)
{
this person is not a manager;
}
If you feel you really must have that extra field then you can still populate it with the method above.
If you are using MySQL, I think you are looking for:
IFNULL(expr1,expr2)
where:
expr1 == null condition (in your case NULL)
expr2 == replacement value
Source: Control Flow Functions

Sort field in relations table (one to many), how to insert the sort number?

I have two tables, content and images (and a ContentImages table for the one to many relation, so that's actually 3 tables).
The following code saves the relation (in the action > updateContentFromRequest() ):
$ids = $this->getRequestParameter('contentImages');
if( isset($ids) ){
$ImagesTable = Doctrine::getTable('Content')->getRelation('Images')->getTable();
$associationName = Doctrine::getTable('Content')->getRelation('Images')->getAssociationTable()->getOption('name');
$this->content->$associationName->delete();
foreach ($ids as $id){
$id = explode('/', $id);
$this->content->get('Images')->add($ImagesTable->find($id));
}}
I changed the model to include a sort field in the ContentImages table:
content_id
image_id
sort (numeric)
The sort number is simply that, a number (0,1,2,3 etc)
$sort = $this->getRequestParameter('contentImagesSort');
How do I save the sort number? I do not want to add a sort field to the Image table because that could create difficulties when images are re-used across more content items. When a content item is new I do not know the ID yet so I'm a bit stumped...
If you have generated models you can add to your setUp method the orderBy parameter:
$this->hasMany('PICTURE as PICTURES', array(
'local' => 'BRAND_ID',
'foreign' => 'PICTURE_ID',
'refClass' => 'BRAND_PICTURE',
'orderBy' => 'your_field DESC'
));
orderBy should do the trick
You should add One to Many associations on your join Table, like:
ContentImages:
relations:
Image:
local: image_id
foreign: id
Content:
local: content_id
foreign: id
As such, you will be able to query directly the join table like this :
$q = Doctrine_Query::create()
->from('Content c')
->leftJoin('c.ContentImages ci')
->leftJoin('c.Images i')
->execute();
Then you can access to ContentImages using
$content->ContentImages->setSort('your sort');
This should do the trick :)
I do things a little a differently from what you've got above so not completely sure this is what you're asking, but can't you just save the object with whatever is needed for it?
$association = // load up existing entry from ContentImages using whatever method
$association->setSort($the_sort_I_want_to_add);
$association->save();
Or querying it...
$association = // load up existing entry from ContentImages using whatever method
$my_unknown_sort = $association->getSort();
Hope that helps.

Resources