How do i count a data that includes another data in sequelize? - javascript

So I was trying to get a record about car dealers who successfully sold a specific car, sorted from how much the dealer successfully sold that car so the dealer that successfully sold the most of that specific car will appear first. The problem is that in the end I also need to group the car id, which makes the counting inaccurate. Can anyone please help me solve this problem where I can just get the record without needing to group the car id as well?
Here is my code:
const data = await models.Car.findAll({
paranoid: false,
attributes: [[Sequelize.fn('COUNT', Sequelize.col('userId')), 'carsCount'], 'userId'],
where: { brandId, groupModelId, status: 2 },
include: [
{
model: models.User,
as: 'user',
required: true,
duplicating: false,
attributes: ['name', 'phone', 'email'],
include: [
{
model: models.UserAddress,
as: 'userAddress'
}
]
}
],
group: ['userId', 'user.id'],
offset,
limit
})
Thank you in advance and sorry if my English is not perfect

Related

Very slow Sequelize findAndCountAll query with nested "include"

I'm very new to using sequelize. I have a query with nested includes as follows:
Site.findAndCountAll({
where,
order: orderCondition,
...(orderAfter ? {} : limits),
include: [
{
model: Version,
as: 'versions',
limit: 1,
separate: true,
order: [['version_id', 'DESC']],
attributes: ['status', 'config'],
include: [
{
model: User,
as: 'updatedByUser',
attributes: ['email'],
paranoid: false,
},
],
},
{
model: Version,
as: 'prod_version',
separate: false,
required: false,
attributes: ['status', 'config'],
where: {
is_prod: 1,
status: 'SUCCESS',
},
include: [
{
model: User,
as: 'updatedByUser',
attributes: ['email'],
paranoid: false,
},
],
}
],
distinct: true,
});
And I have these associations between Site and Version:
Site.hasMany(models.Version, {
foreignKey: 'site_id',
as: 'versions',
});
Site.hasOne(models.Version, {
foreignKey: 'site_id',
as: 'prod_version',
});
This query can take several seconds to load a page when navigating through the pagination. It takes longer the further along I go through the pages. It's fine for the first ~10-15 pages but then starts getting increasingly slower and by page 30-40 it sometimes takes more than 5 seconds to get a response.
I know there's an inherent issue with the way OFFSET works in SQL but I feel it shouldn't be this slow unless I'm hundreds of pages in. How else could I potentially optimize this query? A big part of the slowdown also seems to be the second item in include, prod_version. With that commented out I get a huge improvement of the response time. Is there a more efficient way to achieve the same result?

Limit being applied before order direction in query

I've come across a problem or bug where if I order by an associated column, and limit the results, they appear to be limited first and then ordered.
This doesn't happen when ordering by columns of the model I've queried, just the associated model's column. The order is also perfect if I don't limit the results at all.
Practical example:
Job A (Company 2), Job B (Company 1), Job C (Company 4), Job D (Company 3)
Ordering by Company name with no limit property gives the results: B, A, D, C, as expected
Adding a limit property of 3 seems to return the limited results: A, B, C and then orders them, resulting in B, A, C, when it should be B, A, D
Association:
Company.hasMany(Job, { foreignKey: { name: 'companyId', allowNull: false} });
Job.belongsTo(Company, { foreignKey: { name: 'companyId', allowNull: false} });
Models:
const Company = sequelize.define('company', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
}
});
const Job = sequelize.define('job', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
title: {
type: Sequelize.STRING,
allowNull: false
},
// Lots more boring fields
});
Query & options object:
switch(req.query.orderField) {
case 'company': {
order = [
'company', 'name', orderDirection
];
break;
}
default:
order = [ orderField, orderDirection ];
}
const options = {
attributes: [
'id',
'title',
[Sequelize.fn('date_format', Sequelize.col('job.createdAt'), '%d/%m/%y'), 'jobDate'],
],
order: [ order ],
distinct: true,
include: [
{
model: Company,
attributes: ['name'],
},
]
};
// Commenting out this line works fine
if(req.query.limit) options.limit = parseInt(limit, 10);
if(req.query.index) options.offset = parseInt(index);
const results = await Job.findAndCountAll(options);
I'm pretty stumped tbh, would really appreciate some help!
**Update:
So in order to make the options model more concise, I left out one other included model: Applicant. So Job has an include with both a Company model and an Applicant model (with its own nested model).
It actually looks like this:
include: [
{
model: Company,
attributes: ['name'],
},
{
model: Applicant,
attributes: [
'id',
'cvUrl',
'personId',
[Sequelize.fn('date_format', Sequelize.col('applicants.createdAt'), '%d/%m/%y'), 'createdAt'],
],
include: [
{
model: Person,
attributes: [ 'firstName', 'lastName', 'phone', 'email' ]
}
]
}
]
I didn't think it would make any difference, but I checked out the query, and it does have a subquery: (TLDR for your convenience followed by real query)
TLDR pseudo query:
SELECT job.* ... FROM(SELECT job.id ... FROM jobs AS job LIMIT 0 8) AS job ... JOINS HERE ORDER BY company.name ASC
Actual query:
Executing (default): SELECT `job`.*, `company`.`id` AS `company.id`, `company`.`name` AS `company.name`, `applicants`.`id` AS `applicants.id`, `applicants`.`cvUrl` AS `applicants.cvUrl`, `applicants`.`personId` AS `applicants.personId`, date_format(`applicants`.`createdAt`, '%d/%m/%y') AS `applicants.createdAt`, `applicants->application`.`id` AS `applicants.application.id`, `applicants->application`.`createdAt` AS `applicants.application.createdAt`, `applicants->application`.`updatedAt` AS `applicants.application.updatedAt`, `applicants->application`.`applicantId` AS `applicants.application.applicantId`, `applicants->application`.`jobId` AS `applicants.application.jobId`, `applicants->person`.`id` AS `applicants.person.id`, `applicants->person`.`firstName` AS `applicants.person.firstName`, `applicants->person`.`lastName` AS `applicants.person.lastName`, `applicants->person`.`phone` AS `applicants.person.phone`, `applicants->person`.`email` AS `applicants.person.email` FROM (SELECT `job`.`id`, `job`.`title`, `job`.`wage`, `job`.`location`, `job`.`description`, `job`.`jobType`, `job`.`position`, `job`.`pqe`, `job`.`featured`, `job`.`createdAt`, date_format(`job`.`createdAt`, '%d/%m/%y') AS `jobDate`, `job`.`companyId` FROM `jobs` AS `job` LIMIT 0, 8) AS `job` LEFT OUTER JOIN `companies` AS `company` ON `job`.`companyId` = `company`.`id` LEFT OUTER JOIN ( `applications` AS `applicants->application` INNER JOIN `applicants` AS `applicants` ON `applicants`.`id` = `applicants->application`.`applicantId`) ON `job`.`id` = `applicants->application`.`jobId` LEFT OUTER JOIN `people` AS `applicants->person` ON `applicants`.`personId` = `applicants->person`.`id` ORDER BY `company`.`name` ASC;
So it is limiting the results first.
But here's the fix: Removing the included Applicant model:
(This query isn't so bad)
Executing (default): SELECT `job`.`id`, `job`.`title`, `job`.`wage`, `job`.`location`, `job`.`description`, `job`.`jobType`, `job`.`position`, `job`.`pqe`, `job`.`featured`, `job`.`createdAt`, date_format(`job`.`createdAt`, '%d/%m/%y') AS `jobDate`, `job`.`companyId`, `company`.`id` AS `company.id`, `company`.`name` AS `company.name` FROM `jobs` AS `job` LEFT OUTER JOIN `companies` AS `company` ON `job`.`companyId` = `company`.`id` ORDER BY `company`.`name` ASC LIMIT 0, 8;
But I can't work out why that included Applicant would fix the problem?

How to write formula in ORDER using fields from subquery

I need to implement sorting on computed data from a subquery. There is a request like this:
const res = await db.order.findAndCountAll({
limit: 10,
offset: (page - 1) * 10,
distinct: true,
order: [[db.sequelize.literal('product.taxes * product.count'), 'ASC']],
where,
include: [
{ model: db.location },
{ model: db.address },
{
model: db.order_product,
as: 'products',
required: true,
include: [
{
model: db.product,
include: [{ model: db.location }],
},
],
},
],
});
This code is not working. I get the error:
missing FROM-clause entry for table "product"
In line:order: [[db.sequelize.literal('product.taxes * product.count'), 'ASC']], i want to use to calculate the fields that are in the db.product model which i am accessing in a sub-sub-query. Moreover, this sub-sub-query will return an array, and I need to execute (db.product.amount * db.product.price) for each element of the array and then add up all the resulting products. How to describe this formula in "order"?
Maybe I chose the wrong path. What is the best way to sort by calculated data from a subquery?

Sequelize how to return column of joined table in the results

I'm using the sequelize module for my node.js mvc project and the query i'd like to execute is the following
SELECT answer_text, isNew, name FROM answer JOIN topic ON answer.topic_id = topic.id
answer_text and isNew are columns of the answer table while name is a column that only exists in the topic table.
How can i have the topic table name column appear in the results next to isNew column so that i can access it easily? Does sequelize provide such a feature or it's my responsibility to format the result?
I've tried to add various things in attributes like 'topic.name' but none worked.
The way i've set up the file structure is based on their documentation Sequelize usage with express
var models = require('../models')
var answers = await models.Answer.findAll({
include: [{
model: models.Topic
}],
attributes: [
'answer_text',
'isNew'
]
})
console.log(answers)
The output of the following is
{ answer_text: 'maybe it is robots',
isNew: true,
Topic:
Topic {
dataValues:
{ id: 830,
mid: 'm.0bjmp5',
name: 'Robot Arena',
description:
'Robot Arena is a computer game made by Infogrames. It features robotic combat similar to that of Battlebots Robotica and Robot Wars. There are a number of different chassis and on top of that there are numerous attachments. Weapons accessories tires and other forms of mobility batteries and air tanks are among the customization choices. A sequel called Robot Arena 2 Design and Destroy was made which allows for total customization of your
robot.',
type: 'cvg.computer_videogame' },
_previousDataValues:
{ id: 830,
mid: 'm.0bjmp5',
name: 'Robot Arena',
description:
'Robot Arena is a computer game made by Infogrames. It features robotic combat similar to that of Battlebots Robotica and Robot Wars. There are a number of different chassis and on top of that there are numerous attachments. Weapons accessories tires and other forms of mobility batteries and air tanks are among the customization choices. A sequel called Robot Arena 2 Design and Destroy was made which allows for total customization of your
robot.',
type: 'cvg.computer_videogame' },
_changed: {},
_modelOptions:
{ timestamps: false,
validate: {},
freezeTableName: false,
underscored: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: null,
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: {},
indexes: [],
name: [Object],
omitNull: false,
sequelize: [Sequelize],
hooks: {} },
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: undefined,
includeNames: undefined,
includeMap: undefined,
includeValidated: true,
raw: true,
attributes: undefined },
isNewRecord: false } }
Please try the following sequelize statement -
var answers = await models.Answer.findAll({
include: [{
model: models.Topic,
attributes: ['name']
}],
attributes: [
'answer_text',
'isNew'
],
raw: true
})
I hope it helps!
Working answer:
Sequelize must be required in order to use [sequelize.col('Topic.name'), 'name'] inside attributes so that we can fetch name column of Topic table and rename 'Topics.name' to name. (Tried models.col but it is not a function)
raw: true is required if you want to get only the columns inside answers[0]
attributes:[] is required inside include because if you don't put it the result will include all the columns from the joined table (Topic).
const models = require('../models')
const sequelize = require('sequelize');
var answers = await models.Answer.findAll({
include: [{
model: models.Topic,
attributes: []
}],
attributes: [
'answer_text',
'isNew',
[sequelize.col('Topic.name'), 'name']
],
raw: true
})
console.log(answers[0])
output:
{ answer_text: 'robot arena',
isNew: 'true',
name: 'Robot Arena' }

Chain relationship in keystone.js

var leaves = new keystone.List('leaves');
leaves.add({
createdBy: {
type: Types.Relationship,
ref: 'user-datas',
initial: true,
label: 'Submitted By',
},
});
var userData = new keystone.List('user-datas');
userData.add({
user_id: {
type: Types.Relationship,
ref: 'Employees',
},
});
var Employees = new keystone.List('Employees');
Employees.add({
name: {
type: Types.Name,
required: true,
index: true,
initial: true,
},
});
I have 3 models/list: leaves,user-data, Employees.
So when in admin panel when I want to add a leave it shows Object id of record from user-data.
But is there any way to show name from Employees when entering a new leave. but the still refer to user-data?
I need to show user's name instead of the user ID as shown in the image below.
When you add the relationship for the leaves, you can do something like this:
submittedBy: {type: Types.Relationship, ref: 'User', many: false},
Hope this help. If not, please post more details like the model declaration.
You can achieve it by using relationship.

Categories

Resources