Get Count Of Nested Entities using Laravel Eloquent - mysql

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

Related

N1QL Query to join array fields with an array in another document

I have 3 documents types :
Data
{
"formId": "7508e7b2-bcf7-437b-a206-9fee87256d01",
"dataValues": [
{
"questionId": "Someguid123",
"questionValue": "Question1"
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
],
"lastUpdateDateTime": "2023-01-04T10:56:49Z",
"type": "Data",
"templateId": "41e4cc2c-e9fb-4bdc-9dc2-af19e5988984",
"creationDateTime": "2022-12-28T11:20:46Z"
}
AttachedDocuments
{
"id": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"lastUpdateDateTime": "2023-01-05T11:47:17Z",
"documents": [
{
"isUploaded": false,
"id": "DocumentMetadata::001",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
},
{
"isUploaded": false,
"id": "DocumentMetadata::002",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
}
],
"type": "AttachedDocuments",
"parentDocId": "MyFormData::7508e7b2-bcf7-437b-a206-9fee87256d01",
"creationDateTime": "2022-12-28T11:20:46Z"
}
DocumentMetaData
{
"id": "DocumentMetadata::001",
"type": "DocumentMetadata",
"name": "MyForm_001.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
},
{
"id": "DocumentMetadata::002",
"type": "DocumentMetadata",
"name": "MyForm_002.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
}
Every Data type document has only one AttachedDocuments document with parentDocId* field set to formId field of Data document.
If items in Data.dataValues has a document attached to it, AttachedDocuments.documents array have items with parentId field set to Data.dataValues[i].questionId.
Also every AttachedDocuments.documents[i] item has a DocumentMetadata document with id of AttachedDocuments.documents[i].id field.
I want to have a query which returns all Data.dataValues as an array but containing a field links that contains the DocumentMetadata.name field like below :
[
{
"questionId": "Someguid123",
"questionValue": "Question1",
"links": ["MyForm_001.png", "MyForm_002.png"]
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
]
I tried unnest clause but couldn't output datavalues items without documents. How should I write the query to include those also?
Thank you
Assuming you have a 1:1 relationship between Data & AttachedDocuments, you can try:
CREATE SCOPE default.f;
CREATE COLLECTION default.f.Data;
CREATE COLLECTION default.f.AttachedDocuments;
CREATE COLLECTION default.f.DocumentMetaData;
CREATE INDEX ix1 ON default.f.DocumentMetaData(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues AS dataValues
LET links = (SELECT RAW DocumentMetaData.name
FROM default.f.DocumentMetaData
WHERE DocumentMetaData.parentDocumentId = AttachedDocuments.id
AND id IN ARRAY a.id FOR a IN AttachedDocuments.documents WHEN a.parentId = dataValues.questionId END
)
;
If you have a 1:n relationship between Data & AttachedDocuments but the attachments for a single question are wholly in a single attached document:
CREATE INDEX ix2 ON default.f.AttachedDocuments(parentDocId);
CREATE INDEX ix3 ON default.f.AttachedDocuments(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues as dataValues
LET links = (SELECT RAW md.name
FROM default.f.AttachedDocuments ad JOIN default.f.DocumentMetaData md ON ad.id = md.parentDocumentId
UNNEST ad.documents d
WHERE ad.parentDocId = "MyFormData::"||Data.formId
AND d.id = md.id
AND d.parentId = dataValues.questionId
)
WHERE ANY dv IN AttachedDocuments.documents SATISFIES dv.parentId = dataValues.questionId END
;
If attachments for a single question can be spread over multiple attached documents, add a DISTINCT to the above statement.
HTH.
(You can use the same logic without collections adding appropriate aliasing and type field filtering.)

Query with Dynamic field values of Couchbase

With the below structure of Couchbase bucket, how do I query if the nested doc has dynamic field name?
Here, I would like to return the customer docs who have account in Hyderabad
I tried to query this way but couldn't succeed.
select * from bucket where accounts.$.city = 'Hyderabad'
I was expecting to return the customer doc with email kp711#yahoo.com but couldn't succeed.
Couchbase docs
[
{
"type": "customer",
"customer_id": <UUID4>,
"user_type": "owner",
"first_name": "",
"last_name": "",
"email": "kp711#yahoo.com",
"password": "",
"phone_number": 11111,
"accounts": {
<account_id which is UUID4>: {
"amount": "500",
"city": "Hyderabad"
}
}
},
{
"type": "customer",
"customer_id": <UUID4>,
"user_type": "employee",
"first_name": "",
"last_name": "",
"email": "px800#yahoo.com",
"password": "",
"phone_number": 33333,
"accounts": {
<account_id which is UUID4>: {
"amount": "500",
"city": "Chennai"
}
}
}
]
Is there a way in Couchbase to fetch in this way?
SELECT b.*
FROM bucket AS b
WHERE ANY v IN OBJECT_VALUES(b.accounts) SATISFIES v.city = 'Hyderabad' END;
OR
SELECT b.*
FROM bucket AS b
WHERE ANY n:v IN b.accounts SATISFIES v.city = 'Hyderabad' END;
The answer from VSR provides a neat solution to the problem. Remember also that you will want to provide an index in order to leverage the Couchbase query services optimally. I'm guessing (based on your doc examples) that you will have multiple document types included in your bucket. With that in mind, here is an example index create statement:
create index idxTypeCustomer on bucket(type) where type = 'customer';
Now you can use it as part of your WHERE clause:
SELECT b.*
FROM bucket AS b
WHERE type = 'customer'
AND ANY v IN OBJECT_VALUES(b.accounts) SATISFIES v.city = 'Hyderabad' END;

jslt access parent field in for expression

Hi, I want to use jslt to transform json , but happen an unsolvable problem.
The input json data like this
{
"user_id": "001",
"friends": [{
"friend_id": "002"
}, {
"friend_id": "003"
}, {
"friend_id": "004"
}]
}
Then , what output json data I expected like the follow :
[{
"user_id": "001",
"friend_id": "002"
}, {
"user_id": "001",
"friend_id": "003"
}, {
"user_id": "001",
"friend_id": "004"
}]
In jslt expression , I use expression of for to traverse the array field friends :
[
for (.friends) {
"user_id": .user_id,
"friend_id": .friend_id
}
]
However , the treansform result can't get field user_id
[{
"friend_id": "002"
}, {
"friend_id": "003"
}, {
"friend_id": "004"
}]
How can I access field user_id out of the scope related array field friends ?
Looking forward for your help, thanks !
The other answer is correct, but more complex than it needs to be. This is enough:
let user_id = (.user_id)
[ for (.friends) { "user_id": $user_id , "friend_id" : .friend_id } ]
Note that if you really want to report errors you could do it like this:
if (.user_id and .friends)
let user_id = (.user_id)
[ for (.friends) { "user_id": $user_id , "friend_id" : .friend_id } ]
else if (not(.user_id))
error("user_id field missing")
else
error("friends field missing")
Using error turns this into an exception at the Java level.
The reason the parent operator is not supported is that Jackson doesn't have a parent pointer in its nodes. That's a performance feature, because it means the node can be reused several places, saving CPU and memory.
You'll need to use a variable for the user_id, which you can then reference in the loop.
The following should fit your requirement:
if (.user_id)
let user = .user_id
if (.friends)
[
for (.friends) {
"user_id": $user,
"friend_id": .friend_id
}
]
else
error("missing key 'friends'")
else
error("missing key 'user_id'")

Laravel using avg() function with foreign key

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

Mongo DB issue with findOne query

I am maintaining a rooms table, where it consists of records associated with the conversations. I want to get the room id between two users so using findOne query but it's bringing other records and not satisfying my need.
Suggest me where the query has gone wrong.
If I give the query:
rooms.findOne({ "userId" :"800", "userId" :"600"});
I am expectng conversation id of fsny11z742kpgb9 but it's giving 6puebew70kke29.
{
"_id": ObjectId("571c5724db62826826d28d08"),
"conversationId": "6puebew70kke29",
"userId": "600",
"firstName": "Test",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:18:28.753Z"),
"__v": 0
}
{
"_id": ObjectId("571c5724db62826826d28d09"),
"conversationId": "6puebew70kke29",
"userId": "900",
"firstName": "User",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:18:28.754Z"),
"__v": 0
}
{
"_id": ObjectId("571c574edb62826826d28d0b"),
"conversationId": "fsny11z742kpgb9",
"userId": "600",
"firstName": "FitTest",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:19:10.192Z"),
"__v": 0
}
{
"_id": ObjectId("571c574edb62826826d28d0c"),
"conversationId": "fsny11z742kpgb9",
"userId": "800",
"firstName": "Dev",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:19:10.193Z"),
"__v": 0
}
You have to use aggregation to do so.
rooms.aggregate([
{ $group: { _id: '$conversationId', users: { $push: '$userId' } } },
{ $match: { users: { $all: ['800', '600'] }, groupType: 'PRIVATE' } },
])
The findOne() operation returns the first document according to the natural order which reflects the order of documents on the disk, see mongodb docs.
Second, the query document you provide as parameter to the findOne() operation contains two values for userId, this is not the same as the $in operator. The latter one overrides the first one.
As Mathieu suggested, a proper lookup would be to use an aggregation pipeline with two steps:
rooms.aggregate([
{ $group: { _id: '$conversationId', users: { $push: '$userId' } } },
{ $match: { users: { $all: ['800', '600'] }, groupType: 'PRIVATE' } },
])
create list with id matching the conversation id and a field of type array containing all the userIds ($group stage)
filter out all entries, where the user-array contains the ids of the both users your are looking for. ($match stage)
Bear in mind, that this will return all conversations of both users.