Prisma - Sort by _sum - postgresql

I do have simple groupBy query in my prisma which looks like this:
const groupBy = await prisma.referral.groupBy({
by: ['recommenderId'],
_sum: {
points: true,
},
});
What I am looking for is the way to sort by this _sum value.
The current response is:
{
"groupBy": [
{
"_sum": {
"points": 20000
},
"recommenderId": 3
},
{
"_sum": {
"points": 19000
},
"recommenderId": 2
},
{
"_sum": {
"points": 34000
},
"recommenderId": 1
}
]
}
What I need is to get is:
{
"groupBy": [
{
"_sum": {
"points": 34000
},
"recommenderId": 1
},
{
"_sum": {
"points": 20000
},
"recommenderId": 3
},
{
"_sum": {
"points": 19000
},
"recommenderId": 2
},
]
}
Based on documentation (https://www.prisma.io/docs/concepts/components/prisma-client/filtering-and-sorting#sorting) I tried something like this:
const groupBy = await prisma.referral.groupBy({
by: ['recommenderId'],
_sum: {
points: true,
},
orderBy: [
{
_sum: 'desc',
},
],
});
But with code I'm getting error:
Argument _sum: Got invalid value 'desc' on prisma.groupByReferral.
Provided String, expected ReferralSumOrderByAggregateInput

You can use _sum on different fields at the same time, so you also need to provide field name that you want to sort on:
const groupBy = await prisma.referral.groupBy({
by: ['recommenderId'],
_sum: {
points: true,
},
orderBy: [
{
_sum: {
// Add `points` key here
points: 'desc'
}
},
],
});

Related

mongo push to array in aggregation

I have the following code:
const newArray = [];
companies.items.forEach(async (item) => {
if (item.parentCompanyID) {
newArray.push({
updateOne: {
filter: { id: item?.parentCompanyID },
update: [
{
$push: {
branches: {
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $eq: [{ $type: '$newlyAdded' }, 'missing'] },
},
},
},
],
upsert: true,
},
});
} else {
newArray.push({
updateOne: {
filter: { id: item?.id },
update: [
{
$set: {
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $eq: [{ $type: '$newlyAdded' }, 'missing'] },
},
},
],
upsert: true,
},
});
}
});
await Company.bulkWrite(newArray);
This will go through company.items and for each will add updateOne into newArray which will the goes to bulkWrite.
My problem lies with $push as this needs to be in aggregation pipeline, and when i add the brackets around update it will break with MongooseError: Invalid update pipeline operator: "$push"
Iam sure the script could be simplified but iam still fairly new to mongoDB. What i need is this to insert to Company if the item hasnt got parentCompanyID, if it does have than push to branches array for the relevant Company with id of parentCompanyID.
Sample data from company.items array:
{
id: 5,
name: "Sports"
parentCompanyID: null
},
{
id: 51,
name: "Football"
parentCompanyID: 5
}
And MongoDB for COmpany should look like this:
{
id: 5,
name: "Sports",
parentCompanyID: null,
branches: [{
id: 51,
name: "Football",
parentCompanyID: 5
}]
}
Hope this makes sense. ANy help would be appreciated. I could not find any similar issue and only one i came accross was to use $concatArrays but this wouldnt work either.
Thank you
EDIT:
as per #Takis_ answer thsi now sort of works. Only problem is when $concatArrays does it jobs its not pushing into array as expected from $push. this is the result as of now, insted of branches being one array it has nested arrays. if there are more branches it follows same patter and it could end up with many nested arrays rather than 1 array of objects. any ideas?
{
"id": 29683585,
"name": "123",
"parentId": null,
"newlyAdded": true,
"branches": [
[
null,
{
"id": 29693873,
"name": "245",
"parentId": 29683585
}
],
{
"id": 29695646,
"name": "789",
"parentId": 29683585
}
]
}
This has now been sorted. Thanks to Takis and his pointing to $concatArrays i was able to make this works.
Working code is below:
newArray.push({
updateOne: {
filter: { id: item?.parentCompanyID },
update: [
{
$set: {
branches: {
$concatArrays: [
{ $ifNull: ['$branches', []] },
[
{
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $ne: ['$newlyAdded', null] },
},
],
],
},
},
},
],
upsert: true,
},
});

Mongoose update only fields available in request body

I am trying to update one document using findOneAndUpdate and $set but I clearly missing something very crucial here because the new request is overwriting old values.
My Device schema looks like this:
{
deviceId: {
type: String,
immutable: true,
required: true,
},
version: {
type: String,
required: true,
},
deviceStatus: {
sensors: [
{
sensorId: {
type: String,
enum: ['value1', 'value2', 'value3'],
},
status: { type: Number, min: -1, max: 2 },
},
],
},
}
And I am trying to update the document using this piece of code:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{ $set: req.body },
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
And when I try to send a request from the postman with the body that contains one or multiple sensors, only the last request is saved in the database.
{
"deviceStatus": {
"sensors": [
{
"sensorId": "test",
"status": 1
}
]
}
}
I would like to be able to update values that are already in the database based on req.body or add new ones if needed. Any help will be appreciated.
The documentation said:
The $set operator replaces the value of a field with the specified
value.
You need the $push operator, it appends a specified value to an array.
Having this documents:
[
{
_id: 1,
"array": [
2,
4,
6
]
},
{
_id: 2,
"array": [
1,
3,
5
]
}
]
Using $set operator:
db.collection.update({
_id: 1
},
{
$set: {
array: 10
}
})
Result:
{
"_id": 1,
"array": 10
}
Using $push operator:
db.collection.update({
_id: 1
},
{
$push: {
array: 10
}
})
Result:
{
"_id": 1,
"array": [
2,
4,
6,
10
]
}
you want to using $push and $set in one findOneAndUpdate, that's impossible, I prefer use findById() and process and save() ,so just try
let result = await Device.findById(deviceId )
//implementation business logic on result
await result.save()
If you want to push new sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{
$push: {
"deviceStatus.sensors": { $each: req.body.sensors }
}
},
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
Update to the old answer:
If you want to update sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ "deviceId": deviceId },
{ "deviceStatus": req.body.sensors },
{ upsert: true },
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);

Mongo: Multiple $inc doesn't work in Mongoose

I'm trying to make an $inc to 2 fields, points and sports.wins:
When I try to make it through the mongo shell, it works:
db.getCollection('userStats').update({
uid: ObjectId("5ed1bd8313955cbfc60df96f"),
"sports._id": ObjectId("5ed533c44dcb3efcfe8cb0ec")
},
{
"$inc": { "sports.$[sports].wins" : 1, "points": 10 },
},
{
"arrayFilters": [
{ "sports._id": ObjectId("5ed533c44dcb3efcfe8cb0ec") }
]
}
);
However, when I try to make it using bulkWrite (via Mongoose), it only updates the wins field:
let bulkArray = [];
bulkArray.push({
updateOne: {
filter: {
uid: usersWon[i].uid,
"sports._id": eventData.reference.sport
},
update: {
"$inc": {
"points": 10,
"sports.$[sport].wins": 1,
}
},
arrayFilters: [
{
"sport._id": sportId
}
]
}
});
await UserStats.bulkWrite(bulkArray);
What am I doing wrong? thanks!

Formatting MongoDB aggregation for Highcharts Stacked Columns

I'm attempting to plot a highcharts stacked column chart displaying multiple button counts per table category. For this I've built the following aggregation in mongodb:
var pipeline = [
{
$group: {
_id: {tableId: "$tableId", buttonId: "$buttonId"},
count: {
$sum: 1
}
}
}
];
This gives me a JSON object with the following hierarchy:
0:
count: 5
_id:
buttonId: "buttonId1"
tableId: "Table1"
I'm trying to bring this into a format suited for highcharts, something along these lines:
var buttonPerTable = [{
name: 'buttonId1',
data: [5, 3, 4]
}, {
name: 'buttonId2',
data: [2, 2, 3]
}, {
name: 'buttonId3',
data: [3, 4, 4]
}]
var tableList = ["Table1","Table2","Table3"]
This is what I've tried, but I can't quite figure out how to do it right, I can't manage to get the data into the wished format:
var activityPerTable = [];
var tableList = [];
result.forEach(function(call, i) {
buttonId = call._id.buttonId
activityPerTable[buttonId] = []
activityPerTable[buttonId].data = []
activityPerTable[buttonId].name = buttonId;
activityPerTable[buttonId].data.push(call.count);
tableList.push(call._id.tableId);
});
tableList = Utils.uniqueArray(tableList);
Any help would be appreciated.
Edit:
Here is a chunk of JSON object:
[
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId1"
},
"count": 1
},
{
"_id": {
"tableId": "table2",
"buttonId": "buttonId3"
},
"count": 10
},
{
"_id": {
"tableId": "table2",
"buttonId": "buttonId1"
},
"count": 12
},
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId2"
},
"count": 8
},
{
"_id": {
"tableId": "table1",
"buttonId": "buttonId2"
},
"count": 2
},
{
"_id": {
"tableId": "table3",
"buttonId": "buttonId1"
},
"count": 3
},
{
"_id": {
"tableId": "table3",
"buttonId": "buttonId2"
},
"count": 6
}
]
It would be a bit simpler if the same names were in order side by side. However, you can convert your JSON to the format required by Highcharts in this way:
Highcharts.each(data, function(el) {
if (series.length) {
for (i = 0; i < series.length; i++) {
if (series[i].name === el._id.buttonId) {
series[i].data.push(el.count);
i = series.length + 1;
}
}
}
if (!series.length || i === series.length) {
series.push({
name: el._id.buttonId,
data: [el.count]
});
}
});
Live demo: http://jsfiddle.net/BlackLabel/tnhv8u62/

Finding multiple docs using same id not working, using meteor + react and mongoDB

How do I get the email address of the students in the same class_id, take it as there are more then 2 students in different class in the DB as well?
I have this but it return empty array []
Meteor.users.find({"course_learn_list.$.class_id": {$in: [classId]}},
{field: {"emails.address": 1}}
).fetch()
Collections
{
"_id": "LMZiLKs2MRhZiiwoS",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student1#gmail.com",
"verified": false
}
]
},
{
"_id": "JgfdLKs2MRhZJgfNgk",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student2#gmail.com",
"verified": false
}
]
}
I think you want:
Meteor.users.find({ "course_learn_list.class_id": classId },
{ "course_learn_list.$": 1, "emails.address": 1 }).fetch()
This should find the first instance in each course_learn_list array where the classId is your classId.
In this case you probably don't need to use a projection to get the right answer. Here's an example of extracting the verified email addresses using only the . operator in the selector:
const ids = ['jePhNgEuXLM3ZCt98', 'some-other-id'];
const emails =
Meteor.users.find({ 'course_learn_list.class_id': { $in: ids } })
.fetch()
.map(user => _.findWhere(user.emails, { verified: true }).address);
This works for me!
Meteor.publish("getMyClassStudents", function(classId) {
console.log("Publish getMyClassStudents")
var self = this
if (self.userId) {
var data = Meteor.users.find({
"course_learn_list.class_id": classId
}, {
"fields": {
"emails.address": 1
}
})
return data
}
else {
return self.ready()
}
})