Javascript key array by object and aggregate duplicates - javascript

I retrieve a list of an order object that comes back as array [Orders], this list can be of any size. Each order object is structured as follows. I am trying to extract the customer from the order as essentially a key and group the orders by each customer.
order: {
details: {
price: ""
}
customer: {
name: "blah blah"
email: "blah#gmail.com"
}
}
I need to figure out what the best way to group every order to a specific customer. Ensuring that if multiple orders come back for the same customer they are it ignores duplicates and still adds the orders to an array belonging to each customer. The ideal output would be
customer: {
name: "Blah Blah",
email: "blah#gmail.com",
orders: [Orders]
}

This probably could be found by searching around stack overflow for how to group an array of items, and is probably duplicated...
But, something like this should work for you, assuming that you want to group customers by email.
var result = orders.reduce(customers, order => {
// comparing customers by email
var customer = customers.find(c => c.email === order.customer.email);
if (!customer) {
customer = {
name: order.customer.name,
email: order.customer.email,
orders: [ order ]
};
// or use spread syntax, if you have that available
// customer = { ...order.customer, orders: [ order ] }
customers.push(customer);
} else {
customer.orders.push(order);
}
}, []);

Related

Excluding results from Objection/Knex query based on withGraphFetched results

I have two models in Objection - "brands" and "offers".
Brand:
const { Model } = require('objection')
class Brand extends Model {
static get tableName() {
return 'brands'
}
...
static get relationMappings() {
const Offer = require('./offer-model')
return {
offer: {
relation: Model.HasManyRelation,
modelClass: Offer,
join: { from: 'brands.id', to: 'offers.brand_id' }
}
}
}
}
Offer:
const { Model } = require('objection')
class Offer extends Model {
static get tableName() {
return 'offers'
}
}
A brand has many offers, but I want to get brands which have at least 1 offer using withGraphFetched, excluding brands which have no offers. Here's what I have so far:
const brandModel = this.objection.models.brand
const query = brandModel.query().withGraphFetched('offer')
query.page(page, page_size)
const offers = await query
This returns the "joined" data, but also returns brands which don't have offers. For example:
[{
id:1,
name: 'brand 1',
offers: [{offerId: 1, offerName: 'offer 1'}]
},{
id:2,
name: 'brand 2',
offers: []
}]
In the above data, I don't want the brand with ID 2 to be in the result set.
I am using Objection/Knex to paginate the results, so I can't just exclude the brands with empty object arrays after the query has been executed.
I can achieve this using raw queries, but that means I can't use the Objection dynamic attributes and a few other key parts of Objection.
Thanks!
You can just tack a whereExists onto the query; something like
const query = brandModel.query()
.withGraphFetched('offer')
.whereExists(
(qb) => qb.select('id').from('offers')
.where('offers.brand_id', knex.ref('brands.id'))
);
Even though the whereExists bit is directly Knex, the query still goes through your models so stuff you've defined there should still apply (maybe unless you're doing something very wild that directly affects the columns used inside the whereExists)

mongoose find() sorting and organizing returned results from a product database in js

I have a problem with organizing my mongoDB data to send to my page in my res and cant figure out how to do the correct js. Here is a simplified version of my schema
var productSchema = new mongoose.Schema({
medium: String,
brand: String,
group: String
});
Here is what a typical entry looks like
medium :"Acrylic",
brand :"liquitex",
group :"heavy body"
there are many more entries in the schema, but these are the only ones I need to be able to sort and organize the returned results with. The problem is I have a route that returns all colors in my database and I want to be able to display them in sections on my page that are grouped under Brand, and then has the individual colors listed under the correct group.
The problem is there are paints from other brands that fall into the heavy body group and so when I use a filter function to sort my data by group, some brands get mixed together. I cant filter by brand, because some brands have acrylic and watercolor so then those get lumped together.
I need some way to filter the returned results of a
mongoose.find({})
that can use the group data as a filter, but then filter those results by the brands so they get separated into the correct brand categories.
I have this so far:
this is all a stripped down version of my app.js file:
//finds all colors in the DB
Color.find({}).lean().exec(function( err, colors)
var groups = [];
// find all groups in the databse
colors.forEach( function(color){
groups.push(color["group"]);
});
//returns only unique names to filter out duplicates
var groupTypes = Array.from(new Set(groups));
var tempVariableBrands = [];
// this sorts all returned paints into their respective group, but we get paints from multiple brands under the same group and that is not good
groupTypes.forEach( function(group){
var name = group;
var result = colors.filter(obj => { return obj.group === group });
tempVariable.push( {group : name, result } );
});
// the tempVariable gets sent to my page like so
res.render("landing", {colorEntry:tempVariable} );
and this works fine to allow me to display each paint by its grouping, but that fails when there is more than one paint from a different manufacturer that is considered the same group like a "heavy body". This is my ejs on my page that works fine:
<% colorEntry.forEach( function(entry){ %>
<div class="brandBlock">
<div class="brandTitle">
<span><%=entry.result[0].brand%> - <%=entry.result[0].group%></span>
I for the life of me cant seem to figure out the combination of filter() and maybe map() that would allow this kind of processing to be done.
My database has like 600 documents, colors from a number of different manufacturers and I don't know how to get this as a returned structure: lets say this is a few colors in the DB that get returned from a mongoose find:
[{ medium: "Oil",
brand: "Gamblin",
group: "Artists oil colors"},
{ medium: "Acrylic",
brand: "Liquitex",
group: "Heavy Body"},
{ medium: "Acrylic",
brand: "Golden",
group: "Heavy Body"}
]
i need to organize it like this or something similar. It can be anything that just sorts this data into a basic structure like this, I am not confined to any set standard or anything, this is just for personal use and a site I am trying to build to learn more.
returnedColors = [ { brand: "Gamblin", group: "Artists oil colors", { 50 paints colors returned} },
{ brand: "liquitex" , group: "heavy body", { 20 paint colors returned } },
{ brand: "golden" , group: "heavy body",{ 60 paint colors returned} }
];
I am not a web developer and only write some web code every 6 months or so and have been trying how to figure this out for the last 2 days. I can't wrap my head around some of the awesome filter and map combo's i have seen and cant get this to work.
Any help or advice would be great. I am sure there are many areas for improvement in this code, but everything was working up until I entered paints that were from different brands that had the same group type and i had to try to rewrite this sorting code to deal with it.
It boils down to needing to be able to iterate over the entire set of returned documents from the DB and then sort them based off 2 values.
UPDATE:
I was able to get something that works and returns the data in the format that I need to be able to send it to my ejs file and display it properly. The code is rather ugly and probably very redundant, but it technically works. It starts off by using the group value to run over paints since each set of paints will have a group name, but can sometimes share a group name with a paint from another brand like "heavy body".
groupTypes.forEach( function(group){
var name = group;
var result = colors.filter(obj => { return obj.group === group });
// this gets brand names per iteration of this loop so that we will know if more than one brand of paint
// has the same group identity.
var brands = [];
result.forEach( function(color){
brands.push(color["brand"]);
});
// This filters the brand names down to a unique list of brands
var brandNames = Array.from(new Set(brands));
// if there is more than one brand, we need to filter this into two separate groups
if( brandNames.length > 1){
//console.log("You have duplicates");
brandNames.forEach( x => {
var tmpResult = [...result];
var resultTmp = result.filter(obj => { return obj.brand === x });
result = resultTmp;
//console.log("FILTERED RESULT IS: ", result);
tempVariable.push( {brand: x ,group : name, result } );
result = [...tmpResult];
});
}else{
tempVariable.push( {brand: result[0].brand ,group : name, result } );
}
});
if anyone can reduce this to something more efficient, I would love to see the "better" way or "right" way of doing something like this.
UPDATE2
Thanks to the answer below, I was put on the right track and was able to rewrite a bunch of that long code with this:
Color.aggregate([
{
$sort: { name: 1}
},
{
$group: {
_id: { brand: '$brand', group: '$group' },
result: { $push: '$$ROOT' }
}
},
{ $sort: { '_id.brand': 1 } }
], function( err, colors){
if(err){
console.log(err);
}else{
res.render("landing", {colorEntry:colors, isSearch:1, codes: userCodes, currentUser: req.user, ads: vs.randomAds()} );
}
});
Much cleaner and appears to achieve the same result.
Since you're using MongoDB, "right" way is to utilize an Aggregation framework, precisely, $group stage.
Product.aggregate([{
$group: {
_id: { group: '$group', brand: '$brand' },
products: { $push: '$$ROOT' }
}
}])
This will output array of objects containing every combination of brand and group, and push all relevant products to corresponding subarray.
Combine it with $project and $sort stages to shape your data further.

Converting large number of database results into array of objects in javascript

I am pulling a database query that has the following info:
id, name, roleId, roleTitle
In the query, I am pulling for users and their roles. Each user can have 0 to N number of roles. I want to in the end have an object like this:
{
id
name
roles: [{
id
title
}]
}
What would the most efficient way of doing this be? Currently I am doing something like this:
const data = [];
arr.forEach((u) => {
const index = data.findIndex(x => x.id === u.id);
if (index >= 0) {
data[index].roles.push({ id: u.roleId, title: u.roleTitle });
} else {
data.push({
id: u.id,
name: u.name,
roles: u.roleId ? [{
id: u.roleId,
title: u.roleTitle,
}] : [],
});
}
}
This solution works correctly but wasn't sure if this was the fastest way to get this done if we scale the user numbers to 10k with an average role per user of 3 or 50k and 5 roles per user
Your best bet is actually to do this all in SQL, since you are using PostgreSQL for your database (as mentioned in comments). I don't know the exact names of your tables and columns, so you may need to tweak this, but this will get you what you want:
SELECT json_agg(t)
FROM (
SELECT
u.id,
u.name,
ro.roles
FROM "user" u
LEFT JOIN (
SELECT
ur.user_id,
json_agg(
json_build_object(
'id', r.id,
'title', r.title
)
) AS roles
FROM user_role ur
LEFT JOIN "role" r ON r.id = ur.role_id
GROUP BY ur.user_id
) ro ON ro.user_id = u.id
) t;
SQL Fiddle: http://www.sqlfiddle.com/#!17/5f6ca/11
Explanation
json_build_object will create an object using the name / value pairs specified, so:
json_build_object(
'id', r.id,
'title', r.title
)
Combines the role id and title into a JSON object like this:
{id: 1, title: "Title 1"}
json_agg aggregates multiple rows into a JSON array, so it converts the role objects above into a single column that is an array of role objects per user (thanks to the GROUP BY u.id part of the inner subquery). The inner subquery gives us a result set like this (one row per user)
| user_id | roles |
|---------|------------------------------------------------------|
| 1 | [{id: 1, title: "Role 1"}, {id: 2, title: "Role 2"}] |
Then the subquery is joined to the user table, and all of that is wrapped in another subquery so json_agg can be used on the entire result and return a single json object that is an array of users with roles.
This almost certainly isn't the most efficient possible version but is faster than what you're doing now:
const data = Object.values(arr.reduce((obj, {id, name, roleId, roleTitle}) => {
if (!(id in obj)) {
obj[id] = {
id,
name,
roles: {},
};
}
if (!obj[id].roles[roleId]) {
obj[id].roles[roleId] = {
id: roleId,
title: roleTitle,
};
}
return obj;
}, {}));
By using objects (hashes) instead of arrays, determining if the user is already there or if the user already has a role is a constant-time O(1) operation (the cost of the hashing function). But searching an array, depending on the search method used, is linear in the worst case O(n) and even the best case is O(log n).
You could go down the rabbit hole of micro-optimizations that will change with the wind, but choosing the correct data structures and algorithms will usually get you the most bang for your optimization buck.
I've used Object.values to convert back to an array at the end, if you omit this and just stick with objects it could be even faster.
Hope this helps.
var modified_array = function(xs, key) {
return xs.reduce(function(rv, x) {
obj = (rv[x[key]] = rv[x[key]] || {});
obj.id = x.id;
obj.name = x.name;
obj.roles = obj.roles || []
obj.roles.push({ id: x.roleId, title: x.roleTitle})
return rv;
}, {});
};
arr = [{id:1,name:"abd",roleId: 10,roleTitle: "hello"},
{id:1, name: "abd", roleId: 15,roleTitle: "fello"}]
console.log( Object.values(modified_array(arr, 'id')));

query an array of objects and return a single object - mongodb

I'm having some trouble searching an array I have in my model which currently contains dummy posts.
My posts array looks like this
posts: [
{
image: <filename>,
comments: [],
joined: <number>,
}
]
The posts array atm holds about 10 objects. I need a way to query this array and return a single object. I already tried the answers given on other similar questions, but they all return the whole user which is not what I want.
I tried this:
model
.find(
{ $match : { "posts.image": req.params.image } },
{ $unwind : "$posts" },
{ $match : { "posts.image": req.params.image } }
)
This also returns the whole object including the password, username, etc. I also tried $elemMatch, and no luck.
I'm only expecting it to return one object (not multiple objects) since ill be querying the array with req.params.
Unsure
Not entirely certain if this is what you are asking for, but a way to get a single property value of an array would be done this way
If I am misunderstood please clarify what you are after and I will amend my answer.
var posts = [{
image: "x.jpg",
comments: [],
joined: 12
},
{
image: "x1.jpg",
comments: [],
joined: 121
},
{
image: "ax.jpg",
comments: [],
joined: 2
}
];
for (a = 0; a < posts.length; a++){
console.log(posts[a].image);
}

How can I count all dupe values in a JSON collection?

I would like to count all the companies in the following JSON string. I was first planning on doing it manually with loops but it seems like this is a good chance for me to learn to use map/reduce... How can return something that returns {company: 2}?
[
{
_id: '123',
company: 'Acme'
}
{
_id: '123',
company: 'Innatrode'
}
{
_id: '123',
company: 'Acme'
}
]
If you want to get the number of unique company names, then
use _.pluck to get the company attribute out of the list of company objects
Find the unique values out of them with _.uniq, which will return an array.
Compose a new object with the key `com
console.log({company: _.uniq(_.pluck(companies, "company")).length});
# { company: 2 }

Categories

Resources