Sequelize ORM, get count of associated items with additional where conditions - javascript

I am new to Sequelize ORM so I am struggling to get through it.
I want to get a list of items and count of associated items by applying filter.
Here are my models
const ActionModel = db.define(
'action',
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
...
buttonIndex: { type: Sequelize.INTEGER },
...
},
{
tableName: 'action',
},
)
const PageModel = db.define(
'pages',
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
pageTitle: { type: Sequelize.INTEGER },
pageLink: { type: Sequelize.STRING },
},
{
tableName: 'pages',
},
)
PageModel.hasMany(Action, { as: 'actions', onDelete: 'CASCADE'})
So If I get a list of pages, it will look like this
[
{
"id": 1,
"pageTitle": "Python community",
"pageLink": "https://fairy-dev.io/python",
}
]
The actions are associated with pages as they happen on the pages.
I want to add two more fields to the response.
visitCount and buttonClickCount
visitCount is total number of actions on the page where buttonIndex == 0
buttonClickCount is total number of actions on the page where buttonIndex != 0
So the result will look like this
[
{
"id": 1,
"pageTitle": "Python community",
"pageLink": "https://fairy-dev.io/python",
"visitCount": 5,
"buttonClickCount": 24
},
{
"id": 2,
"pageTitle": "Sequelize community",
"pageLink": "https://fairy-dev.io/sequelize",
"visitCount": 7,
"buttonClickCount": 57
}
]
I know I should use attributes and include but not sure what the exact answer is.
Any help would be appreciated.
Thanks in advance.

Related

Sequelize - Build dynamic where clause with 'Op.or'

I had this code block working with Sequelize v5. But since switching to v6, it seems to be erroring out. I am getting the error: Error: Invalid value { customer_id: 'dg5j5435r4gfd' }.
And here is the code that creates the where condition block:
let whereBlock = {
deleted_at: null,
};
if (args.includeCore) {
if (customerID !== 'all') {
// whereBlock[Op.or] = [
// { customer_id: customerID },
// { customer_id: coreCustomerID },
// ];
whereBlock[Op.or] = [];
whereBlock[Op.or].push({
customer_id: customerID,
});
whereBlock[Op.or].push({ customer_id: coreCustomerID });
}
} else {
whereBlock.customer_id = customerID;
}
I was using the commented code. And then I tried the code below that. Both are producing the same error. But when I remove all that code from the if block and just put in whereBlock.customer_id = customerID;, then it works fine. So I know the issue is how I am constructing the where condition.
Update: As requested, here is my Sheets model where the where clause is being run on.
'use strict';
export default (sequelize, DataTypes) => {
return sequelize.define(
'Sheet',
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
sheet_name: {
type: DataTypes.STRING,
isAlphaNumeric: true,
required: true,
allowNull: true,
len: [3, 80],
},
sheet_file_name: {
type: DataTypes.STRING,
unique: true,
isAlphaNumeric: true,
required: false,
allowNull: true,
},
brand_name: {
type: DataTypes.STRING,
unique: false,
isAlphaNumeric: true,
required: false,
allowNull: true,
},
customer_id: {
// fk in customers table
type: DataTypes.TINYINT(2).UNSIGNED,
required: true,
allowNull: false,
},
chemical_id: {
// fk in loads table
type: DataTypes.SMALLINT.UNSIGNED,
required: true,
allowNull: false,
},
load_id: {
// fk in loads table
type: DataTypes.SMALLINT.UNSIGNED,
required: true,
allowNull: false,
},
active: {
type: DataTypes.BOOLEAN,
required: true,
allowNull: false,
defaultValue: true,
},
created_at: {
type: DataTypes.DATE,
},
updated_at: {
type: DataTypes.DATE,
},
deleted_at: {
type: DataTypes.DATE,
},
},
{
underscored: true,
paranoid: false,
}
);
};
And in my index I have this to associate sheets with customers: db.Sheet.belongsTo(db.Customer);
Also here is the full code where the whereBlock is used, if that helps:
const files = await db.Sheet.findAll({
raw: true,
attributes: [
'sheet_name',
'sheet_file_name',
['brand_name', 'brand'],
'updated_at',
'active',
[Sequelize.col('Chemical.name'), 'chemical'],
[Sequelize.col('Load.value'), 'load'],
],
include: [
{
model: db.Load.scope(null),
required: true,
as: 'Load',
attributes: ['value'],
},
{
model: db.Chemical.scope(null),
required: true,
as: 'Chemical',
attributes: ['name'],
},
],
// model: model,
where: whereBlock,
order: [['active', 'DESC']],
});
TLDR: So here is what it comes down to:
whereBlock = {
deleted_at: null,
customer_id: customerID,
// [Op.or]: [
// { customer_id: customerID },
// { customer_id: coreCustomerID },
// ],
};
That code above works, but the commented code errors out with: Error: Invalid value { customer_id: '123456' }
OK, this is very weird. But I finally figured out the issue!! Was not something I would have thought of, just found it by chance. It was the way I was importing Op from sequelize.
import Op from 'sequelize';
So apparently, that Op object has another object inside it called Op. So when I call my [Op.or], I instead need to do this: [Op.Op.or].
I did try switching my import to import Op.Op from 'sequelize'; and that caused errors. Anyone know how I can properly import the inner object?
Update
OK, so apparently in my other DB files, I was doing the import differently.
export default (db) => {
const Op = db.Sequelize.Op;
That method works to pull in the correct Op object. So there you go. Hopefully this nightmare issue helps someone else in the future.

Sequelize adding many-to-many relation with additional data not working

I have the following models defined:
var Order = sequalize.define(
"order",
{
id: {
type: Sequelize.STRING,
primaryKey: true,
},
menuId: {
type: Sequelize.UUID,
field: "menu_id",
},
},
{
timestamps: false,
}
);
Item.belongsToMany(Order, { through: OrderItem });
Order.belongsToMany(Item, { through: OrderItem });
and
var OrderItem = sequalize.define(
"order_item",
{
orderId: {
type: Sequelize.UUID,
field: "order_id",
},
itemId: {
type: Sequelize.UUID,
field: "item_id",
},
count: {
type: Sequelize.INTEGER,
field: "count",
},
},
{
timestamps: false,
freezeTableName: true,
}
);
I am trying to figure out how to add a order with items without creating items but just adding them to the relationship.
I have this initial format for the order:
{
"id": "som-other-id7",
"items": [{"id": "727f9b52-a88b-4ec3-a68c-98d190564497", "count": 2}, {"id": "7dfd30e7-2d4a-4b16-ae3d-20a330d9b438"}],
"menuId": "7715af03-968f-40e5-9eb2-98016f3deeca"
}
and I try to add it to the db in the following way:
Order.create(orderJson)
.then((order) =>
orderJson.items.map((item) => order.addItem(item.id, { count: item.count }))
)
However the count is not populated. I tried:
using setItem instead of addItem
instead of passing item.id passing {itemId, orderId}
You should call addItem like this:
order.addItem(item.id, { through: { count: item.count }})
See an example in BelongsToMany section

How to Avoid Sequelize JS Associations Including both the Foreign Key and the Included Object

How can I avoid showing both the foreignKey that sequelize creates and the eagerly fetched object through includes?
I have the following model structure:
FormEntry:
owner: User
createdBy: User
modifiedBy: User
formEntryData: [FormEntryData]
I modeled it after reading through SequelizeJS docs and came up with the following:
const User = sequelize.define('user', {
id: {
type: Sequelize.BIGINT(20),
field: 'user_id',
primaryKey: true
},
emailAddress: {
type: Sequelize.STRING(256),
field: 'email_address'
}
}, {
tableName: 'users',
timestamps: false
});
const FormEntryData = sequelize.define('formEntryData', {
id: {
type: Sequelize.BIGINT(20),
field: 'id',
primaryKey: true
},
entryId: {
type: Sequelize.BIGINT(20),
field: 'entry_id'
},
...
}, {
tableName: 'formEntryData',
timestamps: false
});
const FormEntry = sequelize.define('formEntry', {
id: {
type: Sequelize.BIGINT(20),
field: 'entry_id',
primaryKey: true
},
...
}, {
tableName: 'formEntries',
timestamps: false
});
I then need to create the associations to tie the models together and after a lot of trial and error I came up with the following:
FormEntry.hasMany(FormEntryData, {foreignKey: 'entry_id', as: 'FormEntryData'});
FormEntry.belongsTo(User, {foreignKey: 'created_by', as: 'CreatedBy'});
FormEntry.belongsTo(User, {foreignKey: 'modified_by', as: 'ModifiedBy'});
FormEntry.belongsTo(User, {foreignKey: 'owner', as: 'Owner'});
I then was able to query the data by doing the following:
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData"
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
]
})
Unfortunately, my results seem kind of repetitive as it seems to be including both the foreign key and the object that is eagerly fetched.
{
"id": 1472280,
...
"created_by": 26508, <-- repetitive (don't want this)
"modified_by": 26508, <-- repetitive (don't want this)
"owner": null, <-- repetitive (don't want this)
"FormEntryData": [
{
"id": 27164476,
"entryId": 1472280, <-- repetitive (but I want this one)
...
"entry_id": 1472280 <-- repetitive (don't want this)
},
...
],
"CreatedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
},
"Owner": null, <-- repetitive (but I want this one)
"ModifiedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
}
}
You need to exclude specified fields from the query
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData",
attributes: { exclude: ['entry_id'] }
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
],
attributes: { exclude: ['owner', 'created_by', 'modified_by'] }
})

Can't exclude association's fields from select statement in sequelize

I have the following code (simplified):
var group = sequelize.define("group", {
id: {type: DataTypes.INTEGER, autoIncrement: false, primaryKey: true},
name: type: DataTypes.STRING,
parentId: DataTypes.INTEGER
}, { classMethods: {
associate: function (models) {
group.belongsToMany(models.item, { as:'items', foreignKey: 'group_id', through: models.group_item_tie });
}}
});
var group_item_tie = sequelize.define("group_item_tie", {}, {freezeTableName: true});
var item = sequelize.define("item", {
spn: { type: DataTypes.INTEGER, autoIncrement: false, primaryKey: true },
}, { classMethods: {
associate: function (models) {
item.belongsToMany(models.group, { foreignKey: 'spn', through: models.group_item_tie });
}}
});
When I try to return some records with relationships, let's say like this:
dbcontext.group.findAll({
where: { id: 6 },
include: [{
model: dbcontext.item,
as: 'items',
attributes: ['spn']
}]
})
I also get in result the fields from a tie table group_item_tie:
[{
"id": 6,
"name": "abc",
"parentId": 5,
"createdAt": "2015-05-06T15:54:58.000Z",
"updatedAt": "2015-05-06T15:54:58.000Z",
"items": [
{ "spn": 1,
"group_item_tie": {
"createdAt": "2015-05-06 15:54:58.000 +00:00",
"updatedAt": "2015-05-06 15:54:58.000 +00:00",
"group_id": 6,
"spn": 1
}
},
{ "spn": 2,
"group_item_tie": {
"createdAt": "2015-05-06 15:54:58.000 +00:00",
"updatedAt": "2015-05-06 15:54:58.000 +00:00",
"group_id": 6,
"spn": 2
}
},
I see it in generated sql query. How to exclude those from select statement? I've tried a few other things but was not successful.
I hope there is something cleaner then just doing:
delete item.group_item_tie;
I'm going to answer myself as it might be useful to someone in future. So according to #3664, #2974 and #2975 the answer is the following (thanks to mickhansen):
include: [{
model: dbcontext.item,
as: 'items',
attributes: ['spn'],
through: {
attributes: []
}
}]
And soon it will be documented.
I realize this thread is a bit outdated, but since this is high in the Google search results and I struggled to find the answer myself, I thought I'd add this here.
If you're using Model.getAssociatedModel() or Model.$get() (for sequelize-typescript), the current answers listed will not work for this use case. In order to hide the model associations you need to add joinTableAttributes: []
Example:
Model.getAssociatedModel({
joinTableAttributes: []
})
Example:
Model.$get('property', <any>{
joinTableAttributes: []
});
At the time of this post, joinTableAttributes is not included in the sequelize-typescript types hence the <any>
through: {
attributes: []
}
works for me

How to read the referenced object when it is nested in ExtJS 5.0.0

Below code works fine with the data as in ReadOrder.json (below), however how to read the associated object when it is nested inside another object as in ReadOrderNested.json(below, within 'collection').
Question is more specifically can we use a mapping property or proxy's reader config with rootProperty (tried this approach with no luck)
Sencha fiddle : https://fiddle.sencha.com/#fiddle/9fb
Extjs version : 5.0.0
//Base Model
Ext.define('MyApp.model.Base', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'int'
}],
schema: {
namespace: 'MyApp.model'
}
});
//Order Model
Ext.define('MyApp.model.Order', {
extend: 'MyApp.model.Base',
fields: [{
name: 'customer',
type: 'string'
}, {
name: 'paymentStatus',
type: 'string'
}]
});
//PaymentDetail Model
Ext.define('MyApp.model.PaymentDetail', {
extend: 'MyApp.model.Base',
fields: [{
name: 'orderId',
reference: 'Order'
}, {
name: 'cardNumber',
type: 'string'
}, {
name: 'status',
type: 'string'
}]
});
Ext.define('MyApp.store.OrderStore', {
extend: 'Ext.data.Store',
model: 'MyApp.model.Order',
proxy: {
type: "rest",
url: 'Order.json',
appendId: false,
api: {
create: undefined,
read: 'ReadOrder.json',
update: 'UpdateOrder.json',
destroy: undefined
},
reader: {
type: 'json',
rootProperty: 'order'
},
writer: {
writeAllFields: true,
allDataOptions: {
persist: true,
associated: true
}
}
},
});
Ext.application({
name: 'MyApp',
launch: function() {
var orderStore = Ext.create('MyApp.store.OrderStore');
orderStore.load({
callback: function(records) {
var order = this.first();
debugger;
var paymentDetailList = order.paymentDetails();
paymentDetailList.each(function(paymentDetail) {
//Print initial values of payment detail
console.log(paymentDetail.get('cardNumber'));
console.log(paymentDetail.get('status'));
})
}
});
}
});
Data : ReadOrder.json
{ "success": true,
"order": [{
"id": 1,
"customer": "Philip J. Fry",
"paymentStatus": "AWAIT_AUTH",
"paymentDetails": [{
orderId : 1,
"cardNumber": "4111111111",
"status": 'CREATED'
}, {
orderId : 1,
"cardNumber": "4222222222",
"status": "CREATED"
}]
}]
}
How to read with this data when the associated object is nested inside 'collection', ReadOrderNested.json:
{ "success": true,
"order": [{
"id": 1,
"customer": "Philip J. Fry",
"paymentStatus": "AWAIT_AUTH",
"paymentDetails": {
"collection" : [{
orderId : 1,
"cardNumber": "4111111111",
"status": 'CREATED'
}, {
orderId : 1,
"cardNumber": "4222222222",
"status": "CREATED"
}]}
}]
}
I am using ExtJS 4, dunno whether there is a difference. I am using a model with fields like this:
fields: [{
name: 'id',
type: 'int'
},{
name: 'paymentDetails'
}],
and when loading one model into a form
form.load(record);
Ext.getStore("paymentDetailsStore").removeAll();
Ext.getStore("paymentDetailsStore").loadRawData(record.get("paymentDetails"));
with paymentDetailsStore bound to a grid which is in the same window as the form.

Categories