I have a model called Business, a business can have several services they provide. I have another model called Payments. Payment holds a record of what service people pay for. A service can have many Payments. I intend to fetch top 10 and worst 10 businesses based on payments received. The code below works fine, but it is very inefficient. I have to loop through the whole data to retrieve the information I need. Any more efficient way of achieving this?
$businesses = Business::with(['services'])->get();
foreach($businesses as $business){
$id = $business->id;
$name = $business->display_name;
$services = $business->services;
$businessRevenue = 0;
if(count($services)>0){
foreach($services as $service){
$serviceId = $service->id;
$totalAmount = PaymentTransaction::whereHas('invoice', function($query) use ($serviceId){
$query->where('product_code_id', $serviceId);
})->where('amount_paid', ">", 0)->sum('amount_paid');
$businessRevenue= $businessRevenue + $totalAmount;
}
}
$businessArray = (object) array('id'=> $id, 'name'=> $name, 'revenue'=> $businessRevenue);
array_push($transformedBusiness, $businessArray);
}
$topBusiness = $bottomBusiness = $transformedBusiness;
usort($bottomBusiness, function($a, $b) {return strcmp($a->revenue, $b->revenue);});
usort($topBusiness, function($a, $b) {return strcmp($b->revenue, $a->revenue);});
$topBusiness = array_slice($topBusiness, 0, 10);
$bottomBusiness = array_slice($bottomBusiness, 0, 10);
return view('report.department_performance', compact('topBusiness', 'bottomBusiness'));
I guess you could use a join query to get top and lowest 10 businesses directly from database instead of looping all business records and manually calculate their revenue
For top 10 business you can use inner joins for rest of the related tables
$topBusinesses = DB::query()
->select('b.id', 'b.display_name', DB::raw('sum(p.amount_paid) as revenue')
->from('business as b')
->join('service as s', 'b.id', '=', 's.business_id')
->join('invoice as i', 's.id', '=', 'i.product_code_id')
->join('payment_transaction as p', function ($join) {
$join->on('p.id', '=', 'i.payment_transaction')
->where('p.amount_paid', '>', 0);
})
->groupBy('b.id', 'b.display_name')
->orderByDesc('revenue')
->limit(10)
->get();
For lowest 10 business use left joins for invoice and payment_transaction so that if there are no records in these table for a business you will still get these business records
$lowestBusinesses = DB::query()
->select('b.id', 'b.display_name', DB::raw('coalesce(sum(p.amount_paid),0) as revenue')
->from('business as b')
->join('service as s', 'b.id', '=', 's.business_id')
->leftJoin('invoice as i', 's.id', '=', 'i.product_code_id')
->leftJoin('payment_transaction as p', function ($join) {
$join->on('p.id', '=', 'i.payment_transaction')
->where('p.amount_paid', '>', 0);
})
->groupBy('b.id', 'b.display_name')
->orderBy('revenue')
->limit(10)
->get();
I have used MySQL coalesce function to show 0 value in case sum() returns null, If you are using any other database you can use an alternate function.
Related
Right now I have a subquery to get the count of payments for the current month and then getting the 4 products with the highest payment_count. This query works fine but I'm wondering if there's a more simple way to do the same since its getting difficult to read.
$latestPayments = DB::table('payments')
->select('product_id', DB::raw('COUNT(*) as payments_count'))
->whereMonth('created_at', Carbon::now()->month)
->groupBy('product_id');
$trendingProducts = DB::table('products')
->joinSub($latestPayments, 'latest_payments', function ($join) {
$join->on('products.id', '=', 'latest_payments.product_id');
})->orderBy('payments_count', 'DESC')->take(4)->get();
This did it!
$trendingProducts = Product::withCount(['payments' => function($query) {
$query->whereMonth('created_at', Carbon::now()->month);
}])->orderBy('payments_count', 'DESC')->take(4)->get();
If you are using eloquent query with relational database you can do like this:
$latestPaymentWithTrendingProduct = App\Payment::with(['products', function($product) {
$product->orderBy('payments_count', 'DESC')->take(4);
}])->whereMonth('created_at', date('m'))->get()->groupBy('product_id');
This will lessen the code but still do the same thing.
I have a collection and I want to filter that collection based upon "days_since_last_wallet_transaction" by using an array. For example, I have an array $day = array(2,4,6,8). Now I want to get the record where days_since_last_wallet_transaction is in 2,4,6,8. I think I cannot use "days_since_last_wallet_transaction" in where clause. Here is my query:
Customer::select(
'customers.id',
'customers.wallet_amount',
'customers.onesignal_id',
'customers.phone_number',
'customers.name',
DB::raw('DATEDIFF(NOW(), max(wallet_transactions.created_at)) as days_since_last_wallet_transaction')
)
->join('wallet_transactions', function ($join){
$join->on('customers.id', '=', 'wallet_transactions.customer_id')
->where('wallet_transactions.amount', '>', 0);
})
->groupBy('customers.id')
->where('customers.wallet_amount', '>', 0);
Any help would be highly appreciable.
Considering below your array:
$days = [2,4,6,8];
create a raw query as below (Note: you can write this line in query also )
$raw_query = '(DATEDIFF(NOW(), max(wallet_transactions.created_at))) IN ('.implode(',',$days).')';
Your query
Customer::select(
'customers.id',
'customers.wallet_amount',
'customers.onesignal_id',
'customers.phone_number',
'customers.name',
DB::raw('DATEDIFF(NOW(), max(wallet_transactions.created_at)) as days_since_last_wallet_transaction')
)
->join('wallet_transactions', function ($join){
$join->on('customers.id', '=', 'wallet_transactions.customer_id')
->where('wallet_transactions.amount', '>', 0);
})
->where('customers.wallet_amount', '>', 0)
->havingRaw($raw_query)
->groupBy('customers.id');
I have found tons of posts about this issue and yet no solution work.
Maybe its because of my laravel version. Its 5.7.
I have two results, which look like this:
result_a = DB::connection('mysql_live')->table('user_chatmessages')
->where('from_user', $data->chat_from_user)->get();
$result_b = DB::connection('mysql_live')->table('user_chatmessages')
->where('to_user', $data->chat_from_user)->get();
Now I merge them, which works great:
$merged = $result_a->merge($result_b);
$result = $merged->values()->sortByDesc('date_added');
The sorting does not affect the whole result. The problem is that the sorting sorts first result_a and then result_b.
I tried tons of different syntax variants:
$result = $result_a->merge($result_b)->sortBy('date_added');
$merged = $result_a->merge($result_b);
$result = $merged->sortByDesc('date_added');
$merged = $result_a->merge($result_b)->sortByDesc('date_added');
$result = $merged->all();
$result = ($result_a->merge($result_b))->sortBy('date_added');
Probably even more, I am sitting for quite some time on this issue already and all I find are threads where it looks super simple and people tell it works.
I also tried to sort by ID with the same results.
EDIT
The alternative solution provided by #N69S works for the case if you want to get all received and sent messages of a certain user.
But how do you get the messages between two specific users using the same approach? For example I want to get the chatmessages between the user with ID 1 and the user with ID 2 this will not work:
$result = DB::connection('mysql_live')
->table('user_chatmessages')
->where('from_user', $data->chat_from_user)
->where('to_user', $data->chat_to_user)
->orWhere('from_user', $data->chat_to_user)
->orWhere('to_user', $data->chat_from_user)
->orderBy('date_added', 'desc')
->get();
What I am trying right now looks like this:
$result = DB::connection('mysql_live')->table('user_chatmessages')
->where(function ($query) use ($from, $to) {
$query->where('from_user', $from)->where('to_user', $to);
})->orWhere(function ($query) {
$query->where('from_user', $to)->where('to_user', $from);
})->orderBy('date_added', 'asc')->get();
But I get an error: from & to are not defined.
This works perfectly:
$result = DB::connection('mysql_live')->table('user_chatmessages')
->where(function ($query) {
$query->where('from_user', '1')->where('to_user', '2');
})->orWhere(function ($query) {
$query->where('from_user', '2')->where('to_user', '1');
})->orderBy('date_added', 'asc')->get();
Why dont you recover all the results in one query ?
result_a = DB::connection('mysql_live')
->table('user_chatmessages')
->where('from_user', $data->chat_from_user)
->orWhere('to_user', $data->chat_from_user)
->orderBy('date_added', 'desc')
->get();
or user ->orderBy('created_at', 'desc') if you have the default timestamps fields
EDIT
For recovering the chat between two specific users, you need to make use of the parenthesis.
result_a = DB::connection('mysql_live')
->table('user_chatmessages')
->where(function($query) use($data) {
$query->where('from_user', $data->chat_from_user)
->where('to_user', $data->chat_to_user);
})
->orWhere(function($query) use($data) {
$query->where('to_user', $data->chat_from_user)
->where('from_user', $data->chat_to_user);
})
->orderBy('date_added', 'desc')
->get();
you can get the data directly without using merging like this:
result = DB::connection('mysql_live')
->table('user_chatmessages')
->where(function($query) use ($data){
$query->where('from_user', $data->chat_from_user)->orWhere('to_user', $data->chat_from_user)
})->orderBy('date_added','desc')
->get();
If you wan to use two queries then you can use union.
Sub query
$subQuery = DB::connection('mysql_live')
->table('user_chatmessages')
->where('from_user', $data->chat_from_user);
Add sub query by using union on main query
$results = DB::connection('mysql_live')
->table('user_chatmessages')
->where('to_user', $data->chat_from_user)
->union($subQuery) //see
->orderBy('date_added', 'desc') //see
->get();
This should work. You can use what ever queries just make union of them to avoid merge in collections and then sort by date_added
I have many projects and each has many orders, some completed, some not.
I want to order them by the amount of completed orders like this:
$products = $products->orderBy(function($product) {
return $product->orders->where('status', 2)->count();
})->paginate(15);
I know the order call doesn't work like this but this is the best was show the problem. SortBy doesn't work because I want to use pagination.
Finally found a good solution for the problem:
$products = $products->join('orders', function ($join) {
$join->on('orders.product_id', '=', 'products.id')
->where('orders.status', '=', 2);
})
->groupBy('products.id')
->orderBy('count', $order)
->select((['products.*', DB::raw('COUNT(orders.product_id) as count')]))->paginate(50);
Try this if it works:
$categories = Prodcuts::with(array('orders' => function($query) {
$query->select(DB::raw('SELECT * count(status) WHERE status = 2 as count'))
$query->orderBy('MAX(count)')
}))->paginate(15);
return View::make('products.index', compact('categories'));
Note: This is not tested.
From 5.2 on wards you can use the withCount for counting relationship result.
In this case the
$products = $products->withCount(['orders' => function ($query) {
$query->where('status', 2);
}]);
Reference : https://laravel.com/docs/5.2/eloquent-relationships
I'm working on somehow messy project where structure of db is:
Object
id, title, description, url ... [and some more columns]
ObjectMeta
object_id, variable, value
So let's give you:
Object: 1, 'Cool Book', 'This cool book is just for you!', ...
Object Meta:
1, 'author_name', 'John Doe'
1, 'released', 2014
1, 'price', 23.22
1, 'url', 'http://amazon.com/coolbook'
1, 'genre', 3
So I need to perform query which will:
Pull all Objects that has genre 3, then sort those Objects by their released date.
$objs = static::Active()
->where('object', '=', $object)
->where('category','=',$category)
->whereIn('site_id', array(self::$site_id, 0))
->leftJoin('object_meta', function($join)
{
$join->on('object.id','=', 'object_meta.content_id');
})
->where('object_meta.variable', 'genre')
->where('object_meta.value', 3);
->where('object_meta.variable', 'released');
->orderBy('object_meta.value', 'desc');
->groupBy('object.id')
->paginate(20);
This query has 0 results even if there are plenty of those books out there. I know that the second object_meta.variable where is guilty here. How can I write this query to make it work?
Really appreciating your help.
-- Edit
I've created workaround but it's really, really bad workaround (funny thing is that I swear the query above was working for month or so).
$objs = static::Active()
->where('object', '=', $object)
->where('category','=',$category)
->whereIn('site_id', array(self::$site_id, 0))
->leftJoin('object_meta', function($join)
{
$join->on('object.id','=', 'object_meta.content_id');
})
->where('object_meta.variable', 'genre')
->where('object_meta.value', 3);
->groupBy('object.id')
->get();
foreach($objs as $obj)
{
$obj->id = $obj->object_id;
$objs_meta = ObjectMeta::where('object_id',$obj->object_id)->get();
foreach($objs_meta as $obj_meta)
{
$variable = $obj_meta->var;
$obj->$variable = $obj_meta->value;
}
}
$objs = $objs->sortBy(function($role){
return $role->released;
});
$objs = Paginator::make($objs->toArray(), sizeof($objs), $limit);
You need inner join (join()) not leftJoin(), and you need it twice:
$objs = static::Active()
->where('object', '=', $object)
->where('category','=',$category)
->whereIn('site_id', array(self::$site_id, 0))
->join('object_meta as genre', function ($join) {
$join->on('object.id', '=', 'genre.content_id')
->where('genre.variable', '=', 'genre')
->where('genre.value', '=', 3);
})
->join('object_meta as released', function ($join) {
$join->on('object.id', '=', 'released.content_id')
->where('released.variable', '=', 'released');
})
->orderBy('released.value', 'desc');
->select('object.*', 'released.value as released')
->distinct()
// or group by, doesn't matter in your case
// ->groupBy('object.id')
->paginate(20);
Then your objects will have additional property released with appropriate value.
According to the comments:
If you want to dynamically add the joins, then I suggest scopes like below:
public function scopeGenre($query, $value)
{
$query->join('object_meta as genre', function ($join) use ($value) {
$join->on('object.id', '=', 'genre.content_id')
->where('genre.variable', '=', 'genre')
->where('genre.value', '=', $value);
});
}
then:
$query->where(..)->genre(3)->...