How to update multiple mongodb elements with different values - mongodb

I have a simplified order model that looks likes:
order = {
_id: 1,
productGroups:[
{ productId: 1, qty: 3 },
{ productId: 2, qty: 5 }
],
cancels:[]
}
Now I have an api that cancels part of the order.
The request could be something like cancel {productId:1, qty:2}, {productId:2, qty:2} from order where orderId:1. The result should be
order = {
_id: 1,
productGroups:[
{ productId: 1, qty: 2 },
{ productId: 2, qty: 2 }
],
cancels:[
{
productGroups:[
{ productId: 1, qty: 1 },
{ productId: 2, qty: 3 }
]
}
]
}
let order = await Order.findOneAndUpdate(
{
_id: id
},
{
$inc: {
'productGroups.$.qty': cancelQty //this is the part that needs fixing. how do I get different cancelQty according to productId
},
$push: {
cancels: {
products: cancelProductGropus
}
}
},
{ new: true }
);
Now I know I can just findOne, update the model with javascript, and then .save() the model. But if possible I would like to do this update in one go. Or if it is not possible, can I fix the schema so that I can do such update in a single request?

Related

How to turn a list of transactions into an object of token quantities?

So let's say I have a table of transaction data that is shaped like so:
{
tokenAddress: string; // Address of token
to: string; // Address of wallet receiving token
from: string; // Address of wallet sending token
quantity: number; // Number of tokens sent
}
I'd like to perform an aggregation that transforms this data like so
{
tokenAddress: string; // Address of token
walletAddress: string; // Each wallet has a row
quantity: number; // Number of tokens in wallet
}
I am doing this currently by pulling the flat transaction data out and performing a pretty complex reduce in the application code.
export const getAddressesTokensTransferred = async (
walletAddresses: string[]
) => {
const collection = await getCollection('tokenTransfers');
const result = await collection
.find({
$or: [
{ from: { $in: walletAddresses } },
{ to: { $in: walletAddresses } },
],
})
.toArray();
return result.reduce((acc, { tokenAddress, quantity, to, from }) => {
const useTo = walletAddresses.includes(to);
const useFrom = walletAddresses.includes(from);
let existingFound = false;
for (const existing of acc) {
if (existing.tokenAddress === tokenAddress) {
if (useTo && existing.walletAddress === to) {
existingFound = true;
existing.quantity += quantity;
break;
} else if (useFrom && existing.walletAddress === from) {
existingFound = true;
existing.quantity -= quantity;
break;
}
}
}
if (!existingFound) {
if (useTo) {
acc.push({ tokenAddress, walletAddress: to, quantity });
}
if (useFrom) {
acc.push({
tokenAddress,
walletAddress: from,
quantity: quantity * -1,
});
}
}
return acc;
}, [] as { tokenAddress: string; walletAddress: string; quantity: number }[]);
};
I feel like there MUST be a better way to do this within MongoDB, but I'm just not experienced enough with it to know how. Any help is greatly appreciated!
Edit - Adding some sample documents:
Input walletAddresses:
[
'0x72caf7c477ccab3f95913b9d8cdf35a1caf25555',
'0x5b6e57baeb62c530cf369853e15ed25d0c82a866'
]
Result from initial find:
[
{
to: "0x123457baeb62c530cf369853e15ed25d0c82a866",
from: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
quantity: 5,
tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18",
},
{
to: "0x123457baeb62c530cf369853e15ed25d0c82a866",
from: "0x0000000000000000000000000000000000000000",
quantity: 5,
tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
},
{
to: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
from: "0x0000000000000000000000000000000000000000",
quantity: 5,
tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
},
{
to: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
from: "0x0000000000000000000000000000000000000000",
quantity: 5,
tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
}
]
This is a small sample with just two wallets (the other 0x000, and any others not in the walletAddresses array can be discarded essentially), and a single token (there would be many, we would want a row for each of them that have a transaction with the wallets)
The desired result would be
[
{
tokenAddress: '0x86ba9ec85eebe10a9b01af64e89f9d76d22cea18',
walletAddress: '0x72caf7c477ccab3f95913b9d8cdf35a1caf25555',
quantity: 5
},
{
tokenAddress: '0x86ba9ec85eebe10a9b01af64e89f9d76d22cea18',
walletAddress: '0x5b6e57baeb62c530cf369853e15ed25d0c82a866',
quantity: 10
}
]
One option is to "duplicate" the transactions and keep them temporarily per walletAddress. This way we can group them by walletAddress:
db.collection.aggregate([
{
$project: {
data: [
{walletAddress: "$from",
quantity: {$multiply: ["$quantity", -1]},
tokenAddress: "$tokenAddress"},
{walletAddress: "$to",
quantity: "$quantity",
tokenAddress: "$tokenAddress"}
]
}
},
{$unwind: "$data"},
{$group: {
_id: "$data.walletAddress",
quantity: {$sum: "$data.quantity"},
tokenAddress: {$first: "$data.tokenAddress"}
}},
{$match: {
_id: {$in: [
"0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
"0x123457baeb62c530cf369853e15ed25d0c82a866"
]
}
}}
])
See how it works on the playground example

Algolia retrieve results by multiple facets

First of all, I am using Algolia JavaScript API Client V3 (Deprecated)
I have the following records
{
category: SEDAN,
manufacturer: Volkswagen,
id: '123'
},
{
category: COUPE,
manufacturer: Renault,
id: '234'
},
{
category: SEDAN,
manufacturer: Fiat,
id: '345'
},
{
category: COUPE,
manufacturer: Peugeot,
id: '456'
},
{
category: SUV,
manufacturer: Volkswagen,
id: '567'
}
I want to query Algolia and get something similar to the following json
{
categories: {
SEDAN: {
count: 2
items: [{
Volkswagen: {
count 1,
items: [{
id: '123'
}]
}
},
{
Fiat: {
count 1,
items: [{
id: '345'
}]
}
}]
},
COUPE: {
count: 2
items: [{
Renault: {
count 1,
items: [{
id: '234'
}]
}
},
{
Peugeot: {
count 1,
items: [{
id: '456'
}]
}
}]
},
SUV: {
count: 1,
items: [{
Volkswagen: {
count 1,
items: [{
id: '567'
}]
}
}]
}
}
}
I have been trying to query Algolia
index
.search({
query: '',
facets: ['category', 'manufacturer'],
attributesToRetrieve: []
})
.then((result) => {
console.log(result.facets);
});
But I am not sure if it is possible to combine the facets
facets added to a query doesn't work that way. It will simply return the record count for each facet value, not the actual records (https://www.algolia.com/doc/api-reference/api-parameters/facets/)
You can create filters around facets and use those to display results by facet value, but there isn't a way to build a single response JSON that is already grouped by facets like you show above. https://www.algolia.com/doc/api-reference/api-parameters/filters/

MongoDB query on objects in an array based on condition

I have a players inventory that looks like this.
let inventory = [ { name: 'Wood', amount: 6 }, { name: 'Stone', amount: 2 } ]
This is the players resources.
I also have a list of craftable items.
{"name":"CraftingTable","craftingReagents":[{"name":"Stone","amount":"2"}]}
{"name":"CraftingTable2","craftingReagents":[{"name":"Wood","amount":"4"}]}
{"name":"CraftingTable3","craftingReagents":[{"name":"Wood","amount":"5"},{"name":"Stone","amount":"2"}]}
The items schema is as such
let itemSchema = new mongoose.Schema(
{
name:String,
craftingReagents: [
{
name: String,
amount: Number
}
]
}
);
I want a query that will return all craftable objects where the players inventory has sufficient resources to do so.
For example with 6 Wood and 2 Stone the query should return all 3 crafting tables, as the player has sufficient resources to craft all three
This is what I have so far, and I am very lost. Please help!
itemModel.find({
craftingReagents: {
$all: [{
$elemMatch: {
name: {
$in: [
'Wood'
]
},
amount: { $lte: 6 }
}
},
{
$elemMatch: {
name: {
$in: [
'Stone'
]
},
amount: { $lte: 2 }
}
}
]
}
});
Heres the thing. The players inventory can change to an infinite number of different resources. AND the craftable objects can have an infinite number of different requirments. I meerly gave an example of what the inventory, and craftable objects could look like.
How do you list documents that the player has enough resources to craft?
Try this :
itemModel.find({
$or: [{ craftingReagents: { $elemMatch: { name: 'Wood', amount: { $lte: 6 } } } },
{ craftingReagents: { $elemMatch: { name: 'Stone', amount: { $lte: 2 } } } }]
})

How to pull array of documents from array of documents?

I have a document like this
{
users: [
{
name: 'John',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'Mike',
id: 3
},
{
name: 'Anna',
id: 4
}
]
}
and I want to remove users from the array with ids 2 and 4. To do that I execute the following code:
const documents = [
{
id: 2
},
{
id: 4
},
]
Model.updateOne({ document_id: 1 }, { $pull: { users: { $in: documents } } });
But it doesn't remove any user.
Could you say me what I'm doing wrong and how to achieve the needed result?
This works if you can redefine the structure of your documents array:
const documents = [2, 4]
Model.updateOne({ document_id: 1 }, { $pull: { users: { id: { $in: documents } } } })

Can I $nin on the current projection in an aggregate pipeline

I have a directional graph to relate people to people, people to children, and people to pets. The relationship model looks like this:
module.exports = mongoose.model('Relationship', {
sourcePerson: {type: Schema.Types.ObjectId, ref: 'Person'},
targetPerson: {type: Schema.Types.ObjectId, ref: 'Person'},
targetChild: {type: Schema.Types.ObjectId, ref: 'Child'},
targetPet: {type: Schema.Types.ObjectId, ref: 'Pet'},
relationshipStatus: {
type: String,
enum: enums.relationshipStatus
}
});
The primary reason I am not using child documents via arrays off of the people is because the maintenance for that type of data model is too high and too strict. I know those statements contrast a little.
You see, if a couple is married then assumptions are made when adding relationships and the logical model becomes a bit more strict, but if they aren't then relationships are very loose and must be for the use case.
So, let's consider 3 people:
_id firstName lastName
1 Bob Smith
2 Jane Smith
3 Billy Bob
and their relationships:
sourcePerson targetPerson relationshipStatus
1 2 M
2 1 M
Take note that 3, Billy Bob, does not have a relationship to any people.
Now, I have a query I'm building that projects a profile for people. Specifically, are they married, do they have pets, and do they have children.
Thus far I've constructed the following aggregate:
db.people.aggregate([
{
$match: {
'_id': {
$in: db.relationships.distinct('sourcePerson', {
relationshipStatus: {
$eq: 'M'
}
})
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
profile: {
married: {
$literal: true
}
}
}
},
{
$match: {
'_id': {
$in: db.relationships.distinct('sourcePerson', {
targetPet: {
$ne: null
}
})
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
profile: {
married: 1,
pets: {
$literal: true
}
}
}
},
{
$match: {
'_id': {
$in: db.relationships.distinct('sourcePerson', {
targetChild: {
$ne: null
}
})
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
profile: {
married: 1,
pets: 1,
children: {
$literal: true
}
}
}
}
])
The immediate problem I have is how can I perform a $nin on the _id of the Person using what I'm going to call the "current projection."
Specifically what I mean is this. If we take this snippet:
db.people.aggregate([
{
$match: {
'_id': {
$in: db.relationships.distinct('sourcePerson', {
relationshipStatus: {
$eq: 'M'
}
})
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
profile: {
married: {
$literal: true
}
}
}
}
...
])
At this point I have a projection of all "married people". From this projection alone I can identify those that are "not married" if I could $nin the current projection.
I'm not sure this is even what I want.
If there is a better overall way, I'm totally open.
Looking forward to your feedback!