sequelize - get AVG of included model - javascript

I have a a schema that is like product: { ... ,ratings: [ {rating: 3} ] }, and using sequalize, I would like to add on a property product.avg_rating using sequelize.
Here is my code:
sequelize.models.product.findAll({
include: [{
model: sequelize.models.rating,
as: 'ratings',
attributes: {
include: ['rating',[sequelize.fn('AVG', 'ratings.rating'), 'avg_rating']]
},
group: ['rating.rating'],//also tried ratings.rating, and just rating
}],
}).then(products=>{...})
But I keep getting this error:
In aggregated query without GROUP BY, expression #1 of SELECT list
contains nonaggregated column 'text_rewriter.languageCombination.id';
this is incompatible with sql_mode=only_full_group_by
Goal Output:
products: [
{product: 'product name',
...
ratings: [...],
avg_rating: 3.7 //THIS AVG IS WHAT I WANT
},{...}
]
any ideas what I am missing? I have seen many examples, but none of them use include like I did that I found.

sequelize.models.product.findAll({
include: [{
model: sequelize.models.rating,
as: 'ratings',
attributes: ['avg_rating'] //this is column name here
}],
}).then(products=>{...})
This will return :-
products: [
{product: 'product name',
ratings: [...],
rating : { //here all the attributes you wanted, in this case only 'avg_rating' }
},
{...}
]
But you have to define relastionship between products table and rating table before using this.
Table : Product have id, name
Table : Rating have id, rating, product_id
In above case relationship will be 'rating.belongsTo(product) OR product.hasMay(rating)'

MySQL implements detection of functional dependence. If the ONLY_FULL_GROUP_BY SQL mode is enabled (which it is by default), MySQL rejects queries for which the select list, HAVING condition, or ORDER BY list refer to non-aggregated columns that are neither named in the GROUP BY clause nor are functionally dependent on them.
The error is related to sql_mode, you need to execute the following command on your database console.
SET GLOBAL sql_mode = '';

Related

how to return values from joined tables in sequelize at the same level as master table

I am trying to find a way how to put all joined tables at the same level as my master table... so far it only results in the nested values in my final object ..
Here is what I have
Orders.findAll({
include: [
{model: Products, attributes: ['product_name']}
],
attributes: ['id_order', 'dtime_order', 'amount']
})
what I am getting is:
[
{
id_order: 1,
dtime_order: '2021-05-24T22:00:00.000Z',
amount: 20,
products: {
product_name: 'Picture'
}
}
]
but what I wanna get is:
[
{
id_order: 1,
dtime_order: '2021-05-24T22:00:00.000Z',
amount: 20,
product_name: 'Picture'
}
]
I tried this How to return result from include model in same level of main model in Sequelize? but unfortunately when I did:
Orders.findAll({
include: [
{model: Products, attributes: []}
],
attributes: ['id_order', 'dtime_order', 'amount', ['products.product_name', 'product_name']]
})
doesn't work for me saying
column "products.product_name" does not exist
There might be a hacky way to modify the object before sending it back in the response .. but I would rather do it within Sequelize ..
any idea is very welcome... thank you guys!
EDIT: adding the generated SQL
Executing (default): SELECT "orders"."id_order", "orders"."dtime_order", "orders"."amount", "products.product_name", FROM "orders" AS "orders" LEFT OUTER JOIN "products" AS "products" ON "orders"."id_order" = "products"."ir_order";
error: Get dashboard data error: column "products.product_name" does not exist
SOLUTION:
I had to use an alias in my association
Orders.hasOne(Products, {as: 'products', ....})
And then use that EXACTLY SAME alias in my include and referencing
include: [{model: Products, attributes: [], as: 'products'}]
And
attributes: [ ... , [Sequelize.col('products.product_name', 'product_name')]
without the raw: true works like a charm :) Thank you #Emma !!!
Please use Sequelize.col to wrap the nested column so that Sequelize can properly alias the column.
Orders.findAll({
include: [
{model: Products, attributes: []}
],
attributes: ['id_order', 'dtime_order', 'amount', [Sequelize.col('products.product_name'), 'product_name']],
raw: true
})

Sequelize order by join table

I have a Duel type, each Duel has a list of players.
This is my query and relationship:
const duel = Duel.findByPk(id, {
include: duelRelations,
});
const duelRelations = [{
model: User,
as: 'players',
include: [{
model: DuelPokemon,
required: false,
as: 'duelPokemons',
}],
}, {
model: DuelActionLog,
required: false,
as: 'logs',
}];
relationship:
// Each duel has many users
Duel.hasMany(User, {
foreignKey: 'duel_id',
as: 'players',
});
I want to get the players array sorted by the createdAt field of the duel_players table.
I have tried different combinations of order value, but nothing worked. No idea how to define it.
Any idea?
Thanks
You should use sequelize.col() to specify the column from the joined table that you want to sort by in the order property of the findByPk options. Each order by entry is an array with two parts (the column, then the order) an you can pass in multiple entries to sort by multiple columns.
I moved the duelRelations into the options to make it easier to read. In your example you are aliasing User as players and DuelPokemon as duelPokemons. Note that you will have needed to define these associations for each Model. Your question mentions duel_players which doesn't exist in your example, but I think you meant to say players.createdAt.
const duel = Duel.findByPk(id, {
include: [
{
model: User,
as: 'players',
include: { // can be an object to include single table
model: DuelPokemon,
required: false,
as: 'duelPokemons',
},
},
{
model: DuelActionLog,
required: false,
as: 'logs',
},
],
order: [[sequelize.col('players.createdAt', 'DESC']],
});

How to create "products filter" efficiently in Node.js and Angular?

I'm creating an angular application (computer online store) with a node/express backened. I have a products page to display all the products in my DB. A product has this model (typescript):
interface Product {
name: string
properties: {name: string, value: string | number}[]
}
I have a section within the page where you can filter products by properties. for instance a user can filter all the CPUs that have 4 cores or 8 cores. right now this is implemented like this:
In the angular application i query ALL THE PRODUCTS of the requested category,
loop through all of them, collect their properties and all the possible values and filter like this...
const products = [
{
name: 'intel cpu 1',
properties: [
{name: 'cores', value: 8},
{name: 'clock speed', value: 2.6}
]
},
{
name: 'intel cpu 2',
properties: [
{name: 'cores', value: 4},
{name: 'clock speed', value: 1.2}
]
}
]
collectPropertiesFromProducts(products)
// RESULT:
[
{property: 'cores', possibleValues: [4,8]},
{property: 'clock speed', possibleValues: [1.2,2.6]}
]
For now it works great, i can filter products easily by the result and it is all dynamic (i can just add a property to a product and thats it).
The problem is that it scales VERY BADLY, because:
I have to query all of the products to know their properties
The more products/properties = more CPU time = blocks main thread
My question is how can i do better? i have a node server so moving all the logic to there its pretty useless, i could also just move the "property collecting" function to a worker thread but again, ill have to query all the products...
Instead of dealing with this in the client or in the service itself, you can let mongodb do the calculations for you. E.g. you could write the following aggregation:
db.getCollection('products').aggregate([{
$unwind: "$properties"
},
{
$project: {
name: "$properties.name",
total: {
$add: ["$properties.value", ]
}
}
}, {
$group: {
_id: "$name",
possibleValues: {
$addToSet: "$total"
}
}
}
])
You could then expose this query through a custom endpoint (e.g. GET /product-properties) on your node-server and consume the response on the client.
You should consider doing multiple requests to the backend:
First:
getQueryParams, a new endpoint which returns your RESULT
Second:
A none filtered request to receive the initial set of products
Third:
When select a filter (based on first request) you do a new request with the selected filter

Best way to design a model for the store for a array?

I need to import the following into a store but I am confused about the correct model or models I need to create.
here is an example of the JSON that is returned from my server. Basically its an array with 2 items, with an array in each. The field names are different in each.
I suspect I need to have more than one model and have a relationship but I am unsure where to start. Any ideas? Thanks
[
firstItems: [
{
name : "ProductA",
key: "XYXZ",
closed: true
},
{
name : "ProductB",
key: "AAA",
closed: false
}
],
secondItems : [
{
desc : "test2",
misc: "3333",
},
{
desc : "test1",
misc: "123"
}
]
]
What you have is not JSON, your opening and ending [] can become JSON by changing them to {} and then using the following models
Then you can model it as
// Abbreviated definitions of Models, it has changed starting at Ext 5
Ext.define('FirstItem', fields: ['name', 'key', 'closed'])
Ext.define('SecondItem', fields: ['desc', 'misc'])
Ext.define('TopLevel', {
hasMany: [
{model: 'FirstItem', name: 'firstItems'},
{model: 'SecondItem', name: 'secondItems'}
]
})
Use the reader for store's proxy, it will create appropriate model on load.
If you need to load already loaded json into the store use loadRawData but reader you will need in any case.

Array of values

For example I have n doc in MongoDB collection
Films = new Mongo.Collection('films');
Film.insert({name: 'name n', actor: 'John'}); // *n
And I want to show array with only name values
var names = ['name 1', 'name 2',..,'name n'];
Any idea how to do it ?
And guys , ols write in comments correct title value of my question, to help other guys to find it, thx :)
You didn't provided any criteria for grouping name as an array.
You can use following query to get all names:
db.collection.distinct("name")
OR you can use MongoDB's aggregation to get all name by grouping them with some condition, if required. The query will be like following:
db.collection.aggregate({
$group: {
_id: null, //add condition if require
name: {
$push: "$name"
}
}
}, {
$project: {
name: 1,
_id: 0
}
})
If you want only distinct name then replace $push with $addToSet.

Categories

Resources