I receive an array of products that I need to search for with Mongoose, so I use Find passing the _id of each product. But when going through the array and trying to search, I only receive the data of the first product, for the others I always receive undefined.
This is my code:
const calc = async (details) => {
let grandSubtotal = 0; }
console.log(details);
for (let i = 0; i < details.length; i++) {
let verifyProduct = await Product.find({ _id: details[i]._id});
console.log(verifyProduct[i].salePrice); //It only shows the first product, the others are undefined
.......
}
In my MongoDB database I have the products all saved with salePrice always, in this way:
{
"_id": "628fa841cde1d960c675ee24",
"barCode": "0000075053765",
"idProduct": "03",
"name": "MALBORO ARTESANAL 20",
"desc": "PAQUETE 20 CIGARROS",
"presentation": "PIECES",
"salePrice": 550,
"purchasePrice": 526,
"stock": 0,
"available": true,
"img": [],
"status": false
}
How can I obtain the salePrice information of all the products that I receive since for now I only receive information on the first one and the others are always undefined?
this because, you are using .find({})
let verifyProduct = await Product.find({ _id: details[i]._id});
You're querying using .find({ _id: details[i]._id}), you will always have result in the form of [{..onevalue at 0..}] because .find() returns result in an []
so, when you execute the loop for the first time, your i will be 0 and so when you access the verifyProduct[0].salePrice it will have value. But when your i become 1, your verifyProduct will still have result at 0 position only.
Fix:
const calc = async (details) => {
let grandSubtotal = 0;
console.log(details);
for (let i = 0; i < details.length; i++) {
let verifyProduct = await Product.findById(details[i]._id);
// no array, so access it directly
console.log(verifyProduct.salePrice);
}
}
since you are querying by _id, you can use .findById({}) instead of .find().
Database query in a for loop is super inefficient. You should use the $in MongoDB operator to select multiple documents at once.
Example
const arrayOfIds = ["631318a217f73aa43a58855d", "63132ba7525da531e171c964"];
Product.find({ _id: { $in: arrayOfIds }});
Related
I need to get the length of a query and return the length and full query, like this:
{
"total": 73,
"votes": [
{ some fields.. },
{ some fields.. },
]
}
my solution is:
const votes = await VotesModel.find();
const total = votes.length;
return {total, votes}
but the question is, can I get the same result in a simple query?
or what would be the correct way to return the length and the query?
You need call .toArray() function.
const votes = await VotesModel.find().toArray();
const total = votes.length;
return {total, votes}
I am querying the database using findOne which will return just one document. Now, I want a few of the fields of that document in one object and the other fields in the other object, both of which are wrapped in a single object. For example I have a table called Bus with the following fields -
_id,
busNo,
city,
agency,
date,
ticketPrice,
helper,
driver,
totalCollection
My find one query returns one document but I want it in the below format -
existingAggr - {
"result": [
{
"_id": "630dcd0c652489bca1b319f7",
"busNo": "123",
"city": "32",
"agency": "58",
"date": "2022-08-29T00:00:00.000Z",
}
],
"aggregates": {
"ticketPrice": 8,
"totalCollection": 402,
"helper": 0,
"driver": 23,
}
}
I want this either with single access of database, or we can do some javascript calculations to further bring my result in this manner, but I cant seem to reach the solution. Currently, I am using the below code -
const res = await Bus.findOne(
{ busNo, date },
{
_id :1,
busNo:1,
city:1,
agency:1,
date:1,
ticketPrice:1,
helper:1,
driver:1,
totalCollection:1
}
);
This would return all the fields in one.
Given a result, you can straightfowardly create new objects from your result.
const res = await BusDayWise.findOne(
{ ...filter },
{ session: mongoSession }
);
const result = [
{
"_id": res._id,
"busNo": res.busNo,
"city": res.city,
"agency": res/agency,
"date": res.date,
}
],
const aggregates =
{
"ticketPrice": res.ticketPrice,
"totalCollection": res.totalCollection,
"helper": res.helper,
"driver": res.driver,
}
More advanced answer
You can have a function that retrieve only certain key from a dictionnary
function subDict(dict, keys){
const newDict = {};
keys.forEach(key => newDict[key] = dict[key]);
return newDict
}
test = {"a": 1, "b": 2, "c": 3}
keys = ["a", "c"];
newTest = subDict(test, keys); // {"a": 1; "c": 3}
So in your case
const result = subDict(res, ["_id", "busNo", "city","agency", "date"]);
const aggregates = subDict(res, ["ticketPrice", "totalCollection", "helper", "driver"]);
This should be pretty straightforward to do with projection which is the act of transforming the shape of the documents.
Your edited question now includes a reference to the simplest type of projection, but there are many more. In your case it looks like you can still use a relatively simple one, try this:
{
"result._id": "$_id",
"result.busNo": "$busNo",
"result.city": "$city",
"result.agency": "$agency",
"result.date": "$date",
"aggregates.ticketPrice": "$ticketPrice",
"aggregates.totalCollection": "$totalCollection",
"aggregates.helper": "$helper",
"aggregates.driver": "$driver",
_id: 0
}
Playground demonstration here.
If you are going to be doing this every time you retrieve the data then you may wish to change the schema of the documents as they are stored in the database. Alternatively, you could create a view that defines this projection which would then be automatically applied every time you query the data and not have to rely on the client to request it.
So i had this kind of const value
const testingObjArr = [
{
asdasdasdasdds:"testing 123"
},
{
bcdefghasdkl:"testing 456"
},
{
asdklqwoepskalx:"testing 678"
},
]
I want to getting the value only and push that into a new array
like this
["testing 123", "testing 456", "testing 678"]
I've tried using Object.values and similar ways but always return the same values when log it.
this is the last method that i used
console.log( "testing objs",Object.values(obj).map(i => i).flat())
I also use this loop to getting the values
const testingObjArrLength = Object.keys(testingObjArr).length;
const finalDatas = [];
for(let x=0; x<testingObjArrLength; x++) {
const datas = Object.values(testingObjArr)[x]
finalDatas.push(datas);
}
console.log("finaldatas", finalDatas);
This is the results in console log
can anyone give me an advice? appreciate that if not tagged this with duplicate, because i've been around searching for the solutions
You could take Array#flatMap with Object.values as callback.
const
data = [{ asdasdasdasdds: "testing 123" }, { bcdefghasdkl: "testing 456" }, { asdklqwoepskalx: "testing 678" }],
values = data.flatMap(Object.values);
console.log(values);
Suppose I have 3 MongDB entries in a movie database:
"books": [
{
"_id": "xxxxxx",
"title": "Fast Five",
"rating": 6
},
{
"_id": "yyyyyy",
"title": "Kill Bill",
"rating": 8
},
{
"_id": "zzzzzzzz",
"title": "Jumanji",
"rating": 5
}
]
I use GraphQL to retrieve the id of multiple movies if their title and rating match the criteria:
query{
getMovies(itemFilterInput: {titles:["Fast Five", "Jumanji"], rating:6}){
_id
title
}
}
This should return Fast Five.
I want this to translate into a mongoDB query like this:
{$and: [{rating:6}, {$or: [{title:"Fast Five", title:"Jumanji"}]}]}
In the backend, I use NodeJS and Mongoose to handle this query and I use something like this:
getMovies: async args => {
try{
let d = args.itemFilterInput;
let filters = new Array();
const keys = Object.keys(d);
keys.forEach((key) => {
// The titles case
if(d[key] instanceof Array){
var sObj = {};
var sObjArray = [];
d[key].map(prop => {
sObj[key] = prop;
sObjArray.push(sObj);
});
filters.push({$or: sObjArray});
}else{
// The rating case
var obj = {};
obj[key] = d[key];
filters.push(obj);
}
});
const items = await Item.find({$and: filters});
return items.map(item => {
return item;
});
} catch (err) {
throw err;
}
}
Unfortunately, this approach does not work.
What is the correct way to nest $or parameters in $and params?
Answer:
First of all, it is better to use the $in operator instead of a $and/$or combination.
And to solve the error: use quotes everywhere to declare the query.
I'm beginner in js. I'm using vuejs, axios and this api https://www.balldontlie.io to work on a homework project. This project must be to consult nba players stats. I need your help to solve problems. Here is my code :
var app = new Vue({
el: '#app',
data: {
teams: null,
players: [],
selectedTeam: null,
selectedTeamPlayers: [],
selection: false,
id : [],
season_averages : []
},
methods: {
getAllPlayers() {
axios.get('https://www.balldontlie.io/api/v1/players?per_page=100').then(response => {
let total = response.data.meta.total_pages;
let req = [];
let url = 'https://www.balldontlie.io/api/v1/players?per_page=100&page=';
for (let i = 1; i <= total; i++) {
req.push(axios.get(url + i));
}
axios.all(req).then(axios.spread((...responses) => {
for (let i = 0; i < responses.length; i++) {
let jsonPlayers = responses[i].data.data;
for (let j = 0; j < jsonPlayers.length; j++) {
this.players.push(jsonPlayers[j]);
this.id.push(jsonPlayers[j].id);
}
}
console.log(this.id);
}));
});
},
getSeasons() {
let seasons = getAllplayers();
let sa = [];
for (var i = 0; i < seasons; i++) {
axios.get("https://www.balldontlie.io/api/v1/season_averages?player_ids[]=" + i).then(response => {
sa[i] = response.data.data;
for (var i = 0; i < sa.length; i++) {
this.season_averages.push(sa[i]);
}
});
}
console.log(season_averages);
}
},
mounted() {
this.getSeasons();
this.getAllPlayers();
}
});
So I'm making request to get data of nba players and teams. In this script, my first function returns a json structure containing only the id of the players. The second one is supposed to return saisons averages of all the players. However, you can only access the stats of a specific players.
I mean, you can access the stats players in function of his id passed in parameter of the url.
Example : https://www.balldontlie.io/api/v1/season_averages?player_ids[]=237
This url display the saisons averages of the player having the id equal to 237.
So what I want to do is to get all of them and to do that I have to get the ID of all the players. This is why I need the first function. I'm gonna use it in the second function to concate each id with the url of the api to. So I can access and store all of them into an array and return all there stats.
My question is how can I use for loop on axios request to get seasons avergages of each players ?
Regards
YT
You can use async/await for this issue.
Let’s say we have an array of fruits we want to get from the fruit basket.
const fruitsToGet = ['apple', 'grape', 'pear']
We simply create a function that returns a specific fruit after some time:
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
const getNumFruit = fruit => {
return sleep(1000).then(v => fruitBasket[fruit])
}
We are going to loop through this array.
const forLoop = async _ => {
console.log('Start')
for (let index = 0; index < fruitsToGet.length; index++) {
// Get num of each fruit
}
console.log('End')
}
In the for-loop, we will use getNumFruit to get the number of each fruit. We’ll also log the number into the console.
Since getNumFruit returns a promise, we can await the resolved value before logging it.
const forLoop = async _ => {
console.log('Start')
for (let index = 0; index < fruitsToGet.length; index++) {
const fruit = fruitsToGet[index]
const numFruit = await getNumFruit(fruit)
console.log(numFruit)
}
console.log('End')
}
When you use await, you expect JavaScript to pause execution until the awaited promise gets resolved. This means awaits in a for-loop should get executed in series.
The result is what you’d expect
This behaviour works with most loops (like while and for-of loops)…
But it won’t work with loops that require a callback. Examples of such loops that require a fallback include forEach, map, filter, and reduce. We’ll look at how await affects forEach, map, and filter in the next few sections.
Source
As far as I can see - you are writing asynchronous code and expecting synchronous execution. When you homework is to include these two functions - then they both should return Promises.
If you just want to stick some stats to a player - a simpler approach could work, too.
const playerurl = 'https://www.balldontlie.io/api/v1/players?search=LeBron';
const statsurl =
'https://www.balldontlie.io/api/v1/season_averages?player_ids[]=';
axios.get(playerurl).then(playerData => {
const statsPromises = playerData.data.data.map(function(player) { // Use Array.map to create promises
return axios.get(statsurl + player.id).then(function(result) {
return new Promise(function(resolve) { // Chain promises
player.stats = result.data.data;
resolve(player); // Finally resolve the player and stats
});
});
});
Promise.all(statsPromises).then(function(playerStats) {
console.log(JSON.stringify(playerStats));
});
});
[
{
"id": 237,
"first_name": "LeBron",
"height_feet": 6,
"height_inches": 8,
"last_name": "James",
"position": "F",
"team": {
"id": 14,
"abbreviation": "LAL",
"city": "Los Angeles",
"conference": "West",
"division": "Pacific",
"full_name": "Los Angeles Lakers",
"name": "Lakers"
},
"weight_pounds": 250,
"stats": [
{
"games_played": 52,
"player_id": 237,
"season": 2019,
"min": "34:53",
"fgm": 9.58,
"fga": 19.52,
"fg3m": 2.12,
"fg3a": 6.15,
"ftm": 3.85,
"fta": 5.52,
"oreb": 0.96,
"dreb": 6.75,
"reb": 7.71,
"ast": 10.73,
"stl": 1.27,
"blk": 0.46,
"turnover": 3.98,
"pf": 1.71,
"pts": 25.12,
"fg_pct": 0.491,
"fg3_pct": 0.344,
"ft_pct": 0.697
}
]
}
]
Maybe there is more to be done - that's where you and homework come in!