Eloquent to count with different status - laravel

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

Related

Laravel Model Collections with multiple tables and where statement

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

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

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

Entity Framework query

I have a piece of code that I don't know how to improve it.
I have two entities: EntityP and EntityC.
EntityP is the parent of EntityC. It is 1 to many relationship.
EntityP has a property depending on a property of all its attached EntityC.
I need to load a list of EntityP with the property set correctly. So I wrote a piece of code to get the EntityP List first.It's called entityP_List. Then as I wrote below, I loop through the entityP_List and for each of them, I query the database with a "any" function which will eventually be translated to "NOT EXIST" sql query. The reason I use this is that I don't want to load all the attached entityC from database to memory, because I only need the aggregation value of their property. But the problem here is, the looping will query the databae many times, for each EntityP!
So I am wondering if anybody can help me improve the code to query the database only once to get all the EntityP.IsAll_C_Complete set, without load EntityC to memory.
foreach(EntityP p in entityP_List)
{
isAnyNotComoplete = entities.entityC.Any(c => c.IsComplete==false && c.parent.ID == p.ID);
p.IsAll_C_Complete = !isAnyNotComoplete;
}
Thank you very much!
In EF 4, you can do:
var ids = entityP_List.Select(p => p.ID);
var q = (from p in entities.entityP
where ids.Contains(p => p.ID)
select new
{
ID = p.ID,
IsAll_C_Complete = !p.entityCs.Any(c => !c.IsComplete)
}).ToList();
foreach (var p in entityP_List)
{
p.IsAll_C_Complete = q.Where(e.ID == p.Id).Single().IsAll_C_Complete;
}
...which will do the whole thing in one DB query. For EF 1, Google BuildContainsExpression for a replacement for the .Contains( part of the above.
I would base EntityP on a SQL View instead of a table. Then I would define the relationship, and aggregate the value for child table within the view.

Codeigniter Pagination: Run the Query Twice?

I'm using codeigniter and the pagination class. This is such a basic question, but I need to make sure I'm not missing something. In order to get the config items necessary to paginate results getting them from a MySQL database it's basically necessary to run the query twice is that right?
In other words, you have to run the query to determine the total number of records before you can paginate. So I'm doing it like:
Do this query to get number of results
$this->db->where('something', $something);
$query = $this->db->get('the_table_name');
$num_rows = $query->num_rows();
Then I'll have to do it again to get the results with the limit and offset. Something like:
$this->db->where('something', $something);
$this->db->limit($limit, $offset);
$query = $this->db->get('the_table_name');
if($query->num_rows()){
foreach($query->result_array() as $row){
## get the results here
}
}
I just wonder if I'm actually doing this right in that the query always needs to be run twice? The queries I'm using are much more complex than what is shown above.
Unfortunately, in order to paginate you must know how many elements you are breaking up into pages.
You could always cache the result for the total number of elements if it is too computationally expensive.
Yeah, you have to run two queries, but $this->db->count_all('table_name'); is one & line much cleaner.
Pagination requires reading a record set twice:
Once to read the whole set so that it can count the total number records
Then to read a window of records to display
Here's an example I used for a project. The 'banner' table has a list of banners, which I want to show on a paginated screen:
Using a public class property to store the total records (public $total_records)
Using a private function to build the query (that is common for both activities). The parameter ($isCount) we pass to this function reduces the amount of data the query generate, because for the row count we only need one field but when we read the data window we need all required fields.
The get_list() function first calls the database to find the total and stores it in $total_records and then reads a data window to return to the caller.
Remember we cannot access $total_records without first calling the get_list() method !
class Banner_model extends CI_Model {
public $total_records; //holds total records for get_list()
public function get_list($count = 10, $start = 0) {
$this->build_query();
$query = $this->db->get();
$result = $query->result();
$this->total_records = count($result); //store the count
$this->build_query();
$this->db->limit($count, $start);
$query = $this->db->get();
$result = $query->result();
return $result;
}
private function build_query($isCount = FALSE) {
$this->db->select('*, b.id as banner_id, b.status as banner_status');
if ($isCount) {
$this->db->select('b.id');
}
$this->db->from('banner b');
$this->db->join('company c', 'c.id = b.company_id');
$this->db->order_by("b.id", "desc"); //latest ones first
}
And now from the controller we call:
$data['banner_list'] = $this->banner_model->get_list();
$config['total_rows'] = $this->banner_model->total_records;
Things get complicated when you start using JOINs, like in my example where you want to show banners from a particular company! You may read my blog post on this issue further:
http://www.azmeer.info/pagination-hitting-the-database-twise/

Resources