Limit being applied before order direction in query - javascript

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?

Related

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?

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

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

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.

Combine two DataTable Columns into one using Javascript

I'm currently working with Groovy/Grails and Javascript.
The code I am currently working with doesn't seem to follow a standard of how DataTables are implemented (at least not what I can see; I'm new to using DataTables and I can't find anything that is similar to what I'm seeing)
I need to combine the Survey Name and Type columns into a single column.
Example data within the "Survey Name / Type" column: "This is my Survey Name (Type)"
Controller:
The Table definitions are declared in the Controller. I'm not quite sure why they are defined here rather than on the gsp...
def impTableConfigs = [
id: [ title: '', sortIndex: 'id', visible: false ],
surveyId: [ title: 'Survey ID Number', sortIndex: 'surveyId', visible: true ],
surveyName: [ title: 'Survey Name', sortIndex: 'surveyName', visible: true],
type: [ title: 'Survey Type', sortIndex: 'type.code', visible: true]
]
def imp = {
// Check is user is logged in
// Check the users role
def dataMemberNames = getDataMemberNames(impTableConfigs)
def editingShtuff = userService.getUserEditing()
withFormat{
html{
return [
title: "Surveys",
editingShtuff : editingShtuff ,
selectedRefugeId: params.filter,
colNames: dataMemberNames,
colTitles: dataMemberNames.collect{
impTableConfigs[it]["title"]
}
]
}
json{
def args = getListDataParams(impTableConfigs, dataMemberNames, editingShtuff)
def results = getFormattedImpListData(
impTableConfigs,
editingShtuff ,
args.refuge,
args.max,
args.offset,
args.sort,
args.sortDir
)
render results as JSON
}
}
}
GSP
$(document).ready(function() {
var ops = {
editAction:'${createLink(controller:"survey", action:"edit")}',
source:'${createLink(controller:"report", action:"imp.json")}?filter='+$("#filter option:selected").val(),
swfUrl:'${resource(dir:'css/data-tables-tabletools',file:'copy_csv_xls_pdf.swf')}',
colNames:<%= colNames as JSON %>,
selectable: false,
useCache: false,
csvAction: '${createLink(action:"imp_download_csv")}',
pdfAction: '${createLink(action:"imp_download_pdf")}',
csvParams: getFilterParam,
pdfParams: getFilterParam
};
// Initialize dataTable
var table = new primr.dataTable("#dataTable", ops, {
aoColumnDefs: [ { aTargets: [8], bSortable: false }]
});
window.table = table;
// Connect filter events
$("#filter").change(function(){
var filter = $("#filter option:selected").val();
table.changeSource("${createLink(controller:"report", action:"imp.json")}?filter=" + filter);
})
});
HTML within the GSP
<table id="dataTable">
<thead>
<tr>
<g:each in="${colTitles}" var="it" status="i">
<th>${it}<sup>${i}</sup></th>
</tr>
</thead>
<tbody>
</tbody>
I'm thinking I need to move the column definitions from the Controller to the GSP and put them in the aoColumnDefs and formatting the surveyName to concatonate the 2 columns together? I'm hesistent to do this, however, as the impTableConfigs variable is used in multiple methods within the Controller. (I've included one such method).
Nevermind, I had already solved the issue but my browser was caching the domain object and controllers.
I put a getter in the Domain Object to concatonate the column values and put it in the impTableConfigs
def impTableConfigs = [
id: [ title: '', sortIndex: 'id', visible: false ],
surveyId: [ title: 'Survey ID Number', sortIndex: 'surveyId', visible: true ],
surveyNameAndType: [title: 'Survey Name/(Type)', sortIndex: 'surveyName', visible: true],
//surveyName: [ title: 'Survey Name', sortIndex: 'surveyName', visible: true ],
//type: [ title: 'Survey Type', sortIndex: 'type.code', visible: true ],
]

Categories

Resources