Laravel using avg() function with foreign key - mysql

I want to get the average rates for each product.
I have a rates table which has a foreign key to product table,
the rates table is similar to this
when I try to get products with this code:
$stocks = Stocks::with('images:url,color', 'tags:tag', 'sizes', 'rates')
->get()
->pluckDistant('tags', 'tag')
->pluckDistant('sizes', 'size');
it returns this
[
{
"id": 10,
"name": "name",
"image": "1564964985mI7jTuQEZxD49SGTce6Qntl7U8QDnc8uhVxedyYN.jpeg",
"images": [
{
"url": "1564964985mI7jTuQEZxD49SGTce6Qntl7U8QDnc8uhVxedyYN.jpeg",
"color": ""
},
{
"url": "1564964985EV20c1jGvCVCzpCv2Gy9r5TnWM0hMpCBsiRbe8pI.png",
"color": ""
},
{
"url": "1564964985iFcMox6rjsUaM8CHil5oQ9HkrsDqTrqLNY1cXCRX.png",
"color": ""
}
],
"tags": [
"عطور"
],
"sizes": [],
"rates": [
{
"id": 1,
"stocks_id": 10,
"rate": 2
},
{
"id": 2,
"stocks_id": 10,
"rate": 4
}
],
}
]
How can I get the average of rates as "rates":3 using the eloquent relations to get them all by sql without php proccessing?

You could leverage something like Appending. Say you have a Product model which has a OneToMany relationship with Rate model.
Your Product model would look something like this:
class Product extends Model
{
protected $with = ['rates'];
protected $appends = ['average_rate'];
public function getAverageRateAttribute()
{
return $this->attributes['average_rate'] = $this->rates->avg('rate');
}
public function rates() {
return $this->hasMany(Rate::class);
}
}
Now anytime you query your products from the database, you'll have the rate appended with the result.
array:7 [▼
"id" => 1
"created_at" => "2019-08-12 14:08:09"
"updated_at" => "2019-08-12 14:08:09"
"average_rate" => 4.5
"rates" => array:2 [▶]
]
However, be aware of causing n+1 problem. If you're using this approach make sure to always eager load your rates.

You could just use join and use aggregate function on rates table.
Stocks::with('images:url,color', 'tags:tag', 'sizes')
->join('rates', 'rates.stocks_id', '=', 'stocks.id')
->selectRaw('stocks.*')
->selectRaw('AVG(rates.rate) as average_rating')
->selectRaw('tags.*')
->selectRaw('sizes.*')
->selectRaw('images.*')
->groupBy('stocks.id')
->get()

Related

How to fetch results from mongodb aggregate with two matching fields

I am trying to get the sum ratings of user admin from this JSON object:
{
"_id": "5a7ef9a0ce8b5c00147c1ef3",
"assessed_by": "admin",
"rating": "Sad",
"assessment_date": "2018-02-10T13:54:53.303Z"
},
{
"_id": "5a7efe6083fec3001465b369",
"assessed_by": "admin",
"rating": "Sad",
"assessment_date": "2018-02-10T14:15:01.485Z"
}
Expected output:
{
"_id" : "admin",
"count" : 2.0
}
I also wanted to sort the assessment_date by range so I used the $and operator but it doesn't seem to work on Node. I have my code here:
const now = moment().utc();
const endDate = moment().utc().subtract(9, 'days');
model.aggregate({
$match: {
$and: [
{rating: "Sad"},
{assessment_date: {$lte: now}},
{assessment_date: {$gte: endDate}}
]
}
}, { $group:
{ _id: "admin",
count: {
$sum: 1 }
}
}, function(err, results){
console.log(results)
})
Mongo syntax seemed to work on Robomongo, but it doesn't work when trying on Node.
Your aggregation pipeline stages need to be in an array.
model.aggregate( [ { <stage> }, ... ] )

Laravel Array & JSON Casting to Algolia

I am trying to send some data along to Algolia through the toSearchableArray. Any strings I have stored in my DB are sending along fine, but I hit a roadblock when trying to push nested JSON data along—the information is being sent as a string with characters escaped.
This is a sample of the nested object that I am storing in my table (MySQL with a JSON data type):
[
{
"id": 19,
"name": "Mathematics",
"short": "Math"
},
{
"id": 23,
"name": "Science",
"short": "Science"
},
{
"id": 14,
"name": "Health and Life Skills",
"short": "Health"
}
]
My model looks like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Resource extends Model
{
use Searchable;
protected $primaryKey = 'objectID';
public function toSearchableArray()
{
$data = $this->toArray();
$data['grades'] = explode(';', $data['grades']);
$data['units'] = explode(';', $data['units']);
return $data;
}
}
I get an output that looks like this:
array:22 [
"objectID" => 1
"name" => "Resource #1"
"slug" => "resource-1"
"write_up" => """
This is an example write up.
"""
"author" => "johnny"
"type_name" => "Lesson Plan"
"language" => "English"
"grades" => array:3 [
0 => "Kindergarten"
1 => "Grade 1"
2 => "Grade 4"
]
"subjects" => "[{"id": 19, "name": "Mathematics", "short": "Math"}, {"id": 23, "name": "Science", "short": "Science"}, {"id": 14, "name": "Health and Life Skills", "short": "Health"}]"
"units" => array:2 [
0 => "Unit A"
1 => "Unit B"
]
"main_image" => "https://dummyimage.com/250x325/000000/fff.png&text=Just+a+Test"
"loves" => 88
"downloads" => 280
"created_at" => "2018-01-01 13:26:47"
"updated_at" => "2018-01-02 10:10:32"
]
As you can see, the 'subjects' attribute is being stored as a string. I know there is attribute casting in 5.5 (I am running 5.5), but I am not too clear on how I would implement the example they have for Array & JSON Casting in my work above. https://laravel.com/docs/5.5/eloquent-mutators#attribute-casting
Would anyone be willing to show me an example?
I'd rely on Attribute Casting for this, add a $casts property in your model and it will be done automatically.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Resource extends Model
{
use Searchable;
protected $primaryKey = 'objectID';
protected $casts = [
'subjects' => 'array',
];
public function toSearchableArray()
{
// Same function as you posted
}
}
You can also do it manually in your toSearchableArray method with $data['subjects'] = json_decode($this->subjects, true);
I answered with more details on this other posts: https://discourse.algolia.com/t/laravel-array-json-casting-to-algolia/4125/2

sql subqueries to mongodb

I am new to MongoDB and I am trying to turn SQL queries into MongoDB queries. But can't seem to find any way to turn a SQL query with a subquery to mongoDB.
for example:
SELECT article, dealer, price
FROM shop
WHERE price=(SELECT MAX(price) FROM shop);
I tried the following, but it doesn't seem to work.
db.shop.group({
"initial": {},
"reduce": function(obj, prev) {
prev.maximumvalueprice = isNaN(prev.maximumvalueprice) ? obj.price :
Math.max(prev.maximumvalueprice, obj.price);
}}).forEach(
function(data){
db.shop.find({
"price": data
},
{
"article": 1,
"dealer": 1,
"price": 1
})
})
How do I convert this SQL query into a MongoDB query?
If you are using MongoDB v. 3.2 or newer you can try to use $lookup.
Try to use aggregation:
$sort your collection by price by DESC;
set $limit to 1 (it will take a first document, which will be with biggest price);
then use $lookup to select the documents from the same collection by max price and set it to tmpCollection element;
$unwind tmpCollection;
$replaceRoot - change document root to $tmpCollection
Example:
db.getCollection("shop").aggregate([
{$sort: {"price":-1}},
{$limit: 1},
{$lookup: {
from: "shop",
localField: "price",
foreignField: "price",
as: "tmpCollection"
}},
{$unwind: "$tmpCollection"},
{$replaceRoot: {newRoot:"$tmpCollection"}}
]);
Looks like you need the aggregation framework for this task using $first within a $group pipeline stage on ordered documents. The initial pipeline step for ordering the documents in the collection is $sort:
db.shop.aggregate([
{ "$sort": { "price": -1 } }, // <-- sort the documents first in descending order
{
"$group": {
"_id": null,
"article": { "$first": "$article" },
"dealer": { "$first": "$dealer" },
"price": { "$first": "$price" }
}
}
])
or using $last
db.shop.aggregate([
{ "$sort": { "price": 1 } }, // <-- note the sort direction
{
"$group": {
"_id": null,
"article": { "$last": "$article" },
"dealer": { "$last": "$dealer" },
"price": { "$last": "$price" }
}
}
])

How to optimize JsonResult after solving Self Referencing?

The following image describes my model relationship between User and Room.
There is a Many to Many relationship between them,
and I have resolved the Self Reference issue by JSON.NET
and adding some configurations to the Application_Start function.
It looks like:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};
}
I defined a API like this to return All the users in database as Json.
public ActionResult Index()
{
return Content(JsonConvert.SerializeObject(db.UserSet), "application/json");
}
The point is , when I get a JsonResult , it looks like
(Every "User" has a navigation attribute "Room" )
[
{
"Room": [
{
"User": [
{
"Room": [],
"Id": 3,
"Name": "waterball",
"Account": "pppaass",
"Password": "123"
}
],
"Id": 1,
"Name": "sadafsa"
}
],
"Id": 2,
"Name": "waterball",
"Account": "safasfasd",
"Password": "123"
},
{
"Room": [
{
"User": [
{
"Room": [],
"Id": 2,
"Name": "waterball",
"Account": "safasfasd",
"Password": "123"
}
],
"Id": 1,
"Name": "sadafsa"
}
],
"Id": 3,
"Name": "waterball",
"Account": "pppaass",
"Password": "123"
}, ........
Obviously , the result looks complex ,
How can I get only the Id,Name but NO User attributes of each Room ?
Or what exactly is the common way people handle with this problem?
===========================================================
I have changed my codes to reach my requirement,
but is this actually the common way to resolve this...?
Or does it have some potential problems?
public ActionResult Index()
{
var result = from u in db.UserSet
select new
{
Id = u.Id,
Account = u.Account,
Password = u.Password,
Room = from r in u.Room
select new
{
Id = r.Id,
Name = r.Name
}
};
return Content(JsonConvert.SerializeObject(result), "application/json");
}

Get Count Of Nested Entities using Laravel Eloquent

I have got 3 database tables clients, coupons and categories
clients table
id,
name,
website,
description,
logo,
slug
categories table
id,
name,
slug
coupons table
id,
client_id,
category_id,
type,
coupon,
title,
description,
link,
views,
slug,
expiry
The relationship is
1) many coupons belongs to client ( many to one relationship)
2) many coupons belongs to category ( many to one relationship)
I am using laravel 5.1.
How can i get the unique count of clients with the clients details , number of coupons a client has and the count of total categories an individual client has.
simplified : i need to get the client details and display that xxx number of coupons are available in the xxx number of categories for a particular client.
so far i can get the unique client details and the number of the coupons count.
public function getAvailableClientsWithItemCountList($page = 1)
{
return Client::join('coupons', 'clients.id', '=', 'coupons.client_id')
->join('categories', 'coupons.category_id', '=', 'categories.id')
->where('coupons.expiry', '>', Carbon::today())
->groupBy('clients.id')
->skip(STORES_PER_REQUEST*($page-1))
->take(STORES_PER_REQUEST)
->get(['clients.id', 'clients.name', 'clients.slug', 'clients.logo', DB::raw('count(clients.id) as dealsCount'), DB::raw('count(categories.id) as categoriesCount')]);
}
STORES_PER_REQUEST = 9 (constant) for paginating.
thanks in advance.
If you have your relationships set up you could do something like:
/**
* Mock relationship for eager loading coupon count
*
* #return mixed
*/
public function couponCount()
{
return $this->hasOne(Coupon::class)
->selectRaw('client_id, count(*) as aggregate')
->groupBy('client_id');
}
public function getCouponCountAttribute()
{
// if relation is not loaded already, let's do it first
if (!$this->relationLoaded('couponCount')) {
$this->load('couponCount');
}
$related = $this->getRelation('couponCount');
// then return the count directly
return ($related) ? (int) $related->aggregate : 0;
}
The above can be used in your Client model, and then you can just alter the couponCount relationship method for your Category model (if you wanted to).
Then add the following for your Category count:
/**
* Mock relationship for eager loading category count
*
* #return mixed
*/
public function categoryCount()
{
return $this->hasOne(Coupon::class)
->selectRaw('category_id, count(*) as aggregate')
->groupBy('client_id, category_id');
}
public function getCategoryCountAttribute()
{
// if relation is not loaded already, let's do it first
if (!$this->relationLoaded('categoryCount')) {
$this->load('categoryCount');
}
$related = $this->getRelation('categoryCount');
// then return the count directly
return ($related) ? (int) $related->aggregate : 0;
}
You can then add a query scope in your Coupon model for getting coupons that haven't expired by something like:
public function scopeActive($query)
{
$query->where('expiry', '>', Carbon::today());
}
If you're only ever going to be getting the count for coupons that haven't expired you can add you can add this straight on to the relationship e.g.
groupBy('client)id')->active()
Now you should be able to eager load the relationship like so:
$clients = Client::with('couponCount', 'clientCount')
->skip(STORES_PER_REQUEST * ($page - 1))
->take(STORES_PER_REQUEST)
->get();
Or you could attach the query scope to the eager load i.e.
$clients = Client::with(['couponCount' => function ($q) {$q->active()}, 'clientCount' => function ($q) {$q->active()}]) ...
Hope this helps!
Okay i figured out myself with additional info as coupon type and the categories available.
The key i did was just added the case in count and removed the join of the categories table.
the final code looked like this
return App\Client::join('coupons', 'clients.id', '=', 'coupons.client_id')
->where('coupons.expiry', '>', \Carbon\Carbon::today())
->orderBy('clients.position', 'desc')
->groupBy('clients.id')
->skip(STORES_PER_REQUEST*(1-1))
->take(STORES_PER_REQUEST)
->get(['clients.id', 'clients.name', 'clients.slug', 'clients.logo', DB::raw('count(clients.id) as total'), DB::raw('count(CASE WHEN coupons.type=\'Coupon\' THEN 1 ELSE NULL END) as couponsCount'), DB::raw('count(CASE WHEN coupons.type=\'Deals\' THEN 1 ELSE NULL END) as dealsCount'), DB::raw('count(Distinct category_id) as categoriesCount')]);
The result was
[{
"id": "8",
"name": "Archies Online",
"slug": "archies-online",
"logo": "Archiesonline.jpg",
"total": "22",
"couponsCount": "20",
"dealsCount": "2",
"categoriesCount": "9"
}, {
"id": "5",
"name": "Shop Clues",
"slug": "shop-clues",
"logo": "Shopclues.jpg",
"total": "24",
"couponsCount": "24",
"dealsCount": "0",
"categoriesCount": "9"
}, {
"id": "6",
"name": "Lens Kart",
"slug": "lens-kart",
"logo": "Lenskart.jpg",
"total": "25",
"couponsCount": "25",
"dealsCount": "0",
"categoriesCount": "8"
}, {
"id": "7",
"name": "Amazer",
"slug": "amazer",
"logo": "Amzer.jpg",
"total": "21",
"couponsCount": "21",
"dealsCount": "0",
"categoriesCount": "8"
}, {
"id": "1",
"name": "Flipkart",
"slug": "flipkart",
"logo": "Flipkart.jpg",
"total": "17",
"couponsCount": "17",
"dealsCount": "0",
"categoriesCount": "9"
}, {
"id": "2",
"name": "Make My Trip",
"slug": "make-my-trip",
"logo": "Makemytrip.jpg",
"total": "11",
"couponsCount": "11",
"dealsCount": "0",
"categoriesCount": "8"
}]
This did the trick for now :);