Get hierarchical structure data nodejs - sequelizejs - mysql - mysql

Here is table folders
id name isSub parentid status
------------------------------------------------------
1 Main Folder1 0 NULL 1
2 Main Folder2 0 NULL 1
3 Sub Main Fol2 1 2 1
4 Sub Main Fol2 1 3 1
5 Sub Main Fol1 1 1 1
6 Sub Main Fol1 1 1 1
7 Main Folder3 0 NULL 1
8 Sub Main Fol1 1 1 1
------------------------------------------------------
This is the table i used for getting hierarchical structure. I'm using nodejs - sequelizejs.
Here is my model
sequelize.define('Folders', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING(100),
allowNull: false,
validate: {
len: [1, 100]
}
},
isSub: {
type: Sequelize.ENUM(),
values: ['0', '1'],
allowNull: false,
defaultValue: '0'
},
parentid: {
type: Sequelize.INTEGER,
allowNull: true,
validate: {
isNumeric: true
}
},
status: {
type: Sequelize.ENUM(),
values: ['0', '1', '2'],
allowNull: false,
defaultValue: '1'
},
},
{
tableName: 'folders'
}
);
Folders.associate = function(models) {
models.Folders.hasMany(models.Folders, {
foreignKey: 'parentid',
as: 'children'
});
};
And my controller function to get the list
models.Folders.findAll({
where: {
isSub : 1,
status : '1',
},
include: [{
model: models.Folders,
as: 'children',
where: {
status: '1'
},
required: false
}],
required: false
}).then((data) => {
data.map((foldData) => {
return Object.assign({}, {
fid: foldData['id'],
fName: foldData['name'],
fisSub: (foldData['isSub'] === '1')? 'Yes': 'No',
subFolders:
foldData['children'].map((sfoldData, index) => {
return Object.assign({}, {
sfid: sfoldData['id'],
sfName: sfoldData['name'],
sisSubFold: (sfoldData['isSub']) === '1'? 'Yes': 'No',
})
}),
});
});
});
By above code I'm getting the result in API calls as below
[
{
"fid": "2",
"fName": "Main Folder2",
"fisSub": "No",
"subCategory": [
{
"sfid": "3",
"sfName": "Sub Main Fol2",
"sisSubFold": "Yes"
}
]
},
{
"fid": "1",
"fName": "Main Folder1",
"fisSub": "No",
"subCategory": [
{
"sfid": "5",
"sfName": "Sub Main Fol1",
"sisSubFold": "Yes"
},
{
"sfid": "6",
"sfName": "Sub Main Fol1",
"sisSubFold": "Yes"
},
{
"sfid": "8",
"sfName": "Sub Main Fol1",
"sisSubFold": "Yes"
}
]
},
{
"fid": "7",
"fName": "Main Folder3",
"fisSub": "No",
"subCategory": []
}
]
Here for "fid": "2" having two level hierarchical structure in table, but I'm getting first level only.
Please correct my code for getting hierarchical structure.
Note: there will many hierarchical structure, not only 2nd / 3rd /soon levels.
I searched many things, but not get any correct one. Please help me out from this.
Thanks in advance

What you want to do is recursive query and it is not supported yet on Sequelize, there is a proposal here https://github.com/sequelize/sequelize/issues/4890 the work around is to use the raw function to write your own CTE request.

Related

Sequelize M:N association not creating through table record

When I create a recipe with associated tags using the through option, no record is created in the joining table in the mysql database I'm connected to.
Here are my models definitions:
export const Recipe = sequelize.define('Recipe', {
// Model attributes are defined here
title: {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: true
},
prepTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
cookTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
totalTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
servings: {
type: DataTypes.INTEGER,
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
},
notes: {
type: DataTypes.STRING, allowNull: true
},
}, {
// Other model options go here
tableName: 'Recipes'
});
export const Tag = sequelize.define('Tag', {
// Model attributes are defined here
name: {
type: DataTypes.STRING,
allowNull: false
},
}, {
// Other model options go here
tableName: 'Tags'
});
export const RecipeTag = sequelize.define('RecipeTag', {
// Model attributes are defined here
}, {
// Other model options go here
timestamps: false,
tableName: 'RecipeTags'
});
Here are my associations:
Recipe.belongsToMany(Tag, {
through: RecipeTag,
foreignKey: 'recipeId',
as: 'tags'
})
Tag.belongsToMany(Recipe, {
through: RecipeTag,
foreignKey: 'tagId',
as: 'recipes'
})
Here is the create call:
Recipe.create(args, {
model: Tag,
through: RecipeTag,
as: 'tags'
});
And here is the data:
{
"title": "Test Recipe",
"image": "test",
"prepTime": 20,
"cookTime": 40,
"totalTime": 60,
"servings": 2,
"rating": 5,
"categoryId": 1,
"tags": [
{
"name": "New tag",
"id": 1
}
],
}
With this set up the create method only creates a new recipe. How can I use the create method to add a record to the joining RecipeTags table at the same time as creating a new recipe? I've managed to get it working by doing something like this:
args.tags.map(async (tag: { tagId: number }) => {
await RecipeTag.create({tagId: tag.tagId, recipeId: recipe.id})
});
But I'd rather have it done in the create if it's possible.
You need to wrap the association options with include.
Recipe.create(args, {
include: {
model: Tag,
through: RecipeTag,
as: 'tags'
}
});
UPDATE:
In order to prevent the duplicates, you can add ignoreDuplicates option and data must include the primary key value.
{
"title": "Test Recipe",
...
"tags": [
{
"name": "New tag",
"id": 1 # this is important
}
]
}
Then
Recipe.create(args, {
include: {
model: Tag,
through: RecipeTag,
as: 'tags',
ignoreDuplicates: true // Add this
}
});
There were some bugs for this option, I suggest you to use the newer version of Sequelize, if you haven't updated lately.

Sequelize find sum in Many to Many Relationship

I need to build a many-to-many relationship between entities Product and Store(Wharehouse).
So I managed to build a relationship with Sequlize like this.
Product.belongsToMany(Store, { through: 'product_store' });
Store.belongsToMany(Product, { through: 'product_store' });
product_store is the table that holds the relationship with Store and Product. When saving a product in a store in need some additional details like quantity so I added a field to the product_store
const Product_Store = sequelize.define("product_store", {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
uid: {
type: STRING,
allowNull: false,
unique: true,
},
ws_price: {
type: DECIMAL(10, 2),
},
discount: {
type: DECIMAL(10, 2),
},
bonus: {
type: DECIMAL(10, 2),
},
quantity: {
type: DECIMAL(10, 2),
},
total: {
type: DECIMAL(10, 2),
},
active: {
type: BOOLEAN,
defaultValue: true,
},
createdAt: {
type: DATE,
},
updatedAt: { type: DATE },
});
I managed to save the product in the store in the following way
await store.addProduct(product, {
through: {
uid: "3462",
ws_price: 100,
discount: 2,
bonus: 1,
quantity: 2000,
total: 200000,
}
})
The values are saved properly and I could get products with store.getProdcuts(). But now I need to query the products and the sum of their quantities in all stores. I've started to query it with
let products = await Product.findAll({
include: {
model: Store,
through: { attributes: ["ws_price", "discount", "bonus", "quantity", "total"] }
}
})
Which queries the product with store details(including the quantity) like this
[
{
"id": 1,
"uid": "4323",
"name": "Sunlight",
"active": true,
"createdAt": "2022-01-24T06:38:47.000Z",
"updatedAt": "2022-01-24T06:38:47.000Z",
"supplierId": null,
"typeId": null,
"unitId": null,
"stores": [
{
"id": 1,
"uid": "122333",
"name": "Store1",
"active": true,
"createdAt": "2022-01-24T06:38:47.000Z",
"updatedAt": "2022-01-24T06:38:47.000Z",
"product_store": {
"ws_price": "150.00",
"discount": "2.00",
"bonus": "1.00",
"quantity": "2000.00",
"total": "300000.00"
}
},
{
"id": 2,
"uid": "34523",
"name": "Store2",
"active": true,
"createdAt": "2022-01-24T06:38:47.000Z",
"updatedAt": "2022-01-24T06:38:47.000Z",
"product_store": {
"ws_price": "300.00",
"discount": "2.00",
"bonus": "1.00",
"quantity": "3000.00",
"total": "300000.00"
}
}
]
}
]
I only need to get the product details and the sum of product in all stores how can I achieve this?. I'm not familiar with using Count/Sum with relationships. Thanks in advance!
The most correct way is to use a subquery via Sequelize.literal like this:
let products = await Product.findAll({
attributes: {
include: [
[Sequelize.literal('(SELECT sum(product_store.quantity) FROM product_store WHERE product_store.product_id=product.id)'), 'product_quantity']
]
}
})

ExtJS model associations with jsonapi specification

We are creating a new version our API (v2) adopting the JSON:API specification (https://jsonapi.org/). I'm not being able to port the ExtJS model associations (belongs_to) to the new pattern.
The ExtJS documentation only shows how to use a nested relation in the same root node (https://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.association.Association).
v1 data (sample):
{
"data": [
{
"id": 1,
"description": "Software Development",
"area_id": 1,
"area": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
},
],
"meta": {
"success": true,
"count": 1
}
}
v2 data (sample):
{
"data": [
{
"id": "1",
"type": "maint_service_nature",
"attributes": {
"id": 1,
"description": "Software Development",
"area_id": 1
},
"relationships": {
"area": {
"data": {
"id": "1",
"type": "area"
}
}
}
}
],
"included": [
{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
}
],
"meta": {
"success": true,
"count": 1
}
}
My model:
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [
{ desc: "Id", name: 'id', type: 'int', useNull: true },
{ desc: "Area", name: 'area_id', type: 'int', useNull: true },
{ desc: "Description", name: 'description', type: 'string', useNull: true, tableIdentification: true }
],
associations: [
{
type: 'belongsTo',
model: 'Suite.model.Area',
foreignKey: 'area_id',
associationKey: 'area',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea',
reader: {
type: 'json',
root: false
}
}
],
proxy: {
type: 'rest',
url: App.getConf('restBaseUrlV2') + '/maint_service_natures',
reader: {
type: 'json',
root: 'data',
record: 'attributes',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors'
}
}
});
Any ideias on how to setup the association to work with the v2 data?
I'm honestly taking a stab at this one... I haven't used Ext JS 4 in years, and I wouldn't structure my JSON like JSON:API does, but I think the only way you can accomplish this is by rolling your own reader class. Given that you have generic properties for your data structure, this reader should work for all scenarios... although, I'm not too familiar with JSON:API, so I could be totally wrong. Either way, this is what I've come up with.
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('MyReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.myReader',
root: 'data',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors',
/**
* #override
*/
extractData: function (root) {
var me = this,
ModelClass = me.model,
length = root.length,
records = new Array(length),
dataConverter,
convertedValues, node, record, i;
for (i = 0; i < length; i++) {
node = root[i];
var attrs = node.attributes;
if (node.isModel) {
// If we're given a model instance in the data, just push it on
// without doing any conversion
records[i] = node;
} else {
// Create a record with an empty data object.
// Populate that data object by extracting and converting field values from raw data.
// Must pass the ID to use because we pass no data for the constructor to pluck an ID from
records[i] = record = new ModelClass(undefined, me.getId(attrs), attrs, convertedValues = {});
// If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
// We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
record.phantom = false;
// Use generated function to extract all fields at once
me.convertRecordData(convertedValues, attrs, record, me.applyDefaults);
if (me.implicitIncludes && record.associations.length) {
me.readAssociated(record, node);
}
}
}
return records;
}
});
Ext.define('Suite.model.Area', {
extend: 'Ext.data.Model',
fields: [{
name: 'type',
type: 'string'
}]
});
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [{
desc: "Id",
name: 'id',
type: 'int',
useNull: true
}, {
desc: "Area",
name: 'area_id',
type: 'int',
useNull: true
}, {
desc: "Description",
name: 'description',
type: 'string',
useNull: true,
tableIdentification: true
}],
associations: [{
type: 'belongsTo',
model: 'Suite.model.Area',
associatedName: 'Area',
foreignKey: 'area_id',
associationKey: 'relationships.area.data',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea'
}],
proxy: {
type: 'rest',
url: 'data1.json',
reader: {
type: 'myReader'
}
}
});
Suite.model.MaintServiceNature.load(null, {
callback: function (record) {
console.log(record.getData(true));
}
});
}
});

Sequelize populate joined table attributes

I have a few tables and I want to do some includes on a joined table, but I can't seem to figure it out. Here are my models:
/* Staff model */
const model = {
fisrName: {
type: DataTypes.INTEGER,
references: { model: 'Roles', key: 'id' },
},
lastName: {
type: DataTypes.INTEGER,
references: { model: 'Profiles', key: 'id' },
}
};
const Staff = createModel('Staff', model, { paranoid: true });
export default Staff
/* Service model */
const model = {
name: {
type: DataTypes.STRING,
},
category: {
type: DataTypes.STRING,
},
description: {
type: DataTypes.STRING,
}
};
const Service = createModel('Service', model, {});
export default Service;
/* Appointment model */
const model = {
endDate: {
type: DataTypes.DATE,
},
startDate: {
type: DataTypes.DATE,
},
day: {
type: DataTypes.DATE,
},
};
const Appointment = createModel('Appointment', model, {})
Appointment.belongsToMany(Service, { through: 'Products', as: 'products' });
export default Appointment;
/* Products model */
const model = {
serviceId: {
type: DataTypes.INTEGER,
},
appointmentId: {
type: DataTypes.INTEGER,
},
staffId: {
type: DataTypes.INTEGER,
references: { model: 'Staff', key: 'id' },
}
};
const Product = createModel('Product', model, {});
Product.belongsTo(Staff, { foreignKey: 'staffId', as: 'staff' });
export default Product;
This is my appointment query, where I include the services array, and on this services array, I have a Products object, and in this object I have a staffId that I want to populate, and I'm not sure how. I have tried different ways, but nothing worked.
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: { attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
],
});
And this is my response:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": 2,
"serviceId": 1,
"appointmentId": 1
}
}]
}
What I want is to do is to populate the staffId from Products with the model from the collection, something like this:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": {
"firstName": "First Name",
"lastName": "Last Name"
},
"serviceId": 1,
"appointmentId": 1
}
}]
}
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: {
attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
include: [{
as: 'Products', model: Product,
include: 'staff'
}]
}
]
});

Sencha Touch: How to get data from complex JSON objects

I am not able retrieve records from complex json objects but i am able to get the data when using an TPL.
Please see the below example codes:
JSON:
{
"lists": [
{
"title": "Groceries",
"id": "1",
"items": [
{
"text": "contact solution - COUPON",
"listId": "1",
"id": "4",
"leaf": "true"
},
{
"text": "Falafel (bulk)",
"listId": "1",
"id": "161",
"leaf": "true"
},
{
"text": "brita filters",
"listId": "1",
"id": "166",
"leaf": "false"
}
]
}
]
Model:
Ext.regModel("TopModel",
{
fields: [
{ name: 'title', type: 'string' },
{ name: 'id', type: 'string' },
],
hasMany: [
{ model: 'SubModel', name: 'items' }
],
proxy: {
type: 'ajax',
url : 'test/lists.json',
actionMethods: {
create: 'POST',
destroy: 'POST',
read: 'POST',
update: 'POST'
},
reader: {
type: 'json',
root: function(data) {
return data.lists || [];
}
}
}
});
Ext.regModel("SubModel",
{
fields: [
{ name: 'text', type: 'string'},
{ name: 'listId', type: 'string'},
{ name: 'id', type: 'string'},
{ name: 'leaf', type: 'string'}
]
});
In my View file, i have defined the store as below.
this.stores = new Ext.data.Store({
clearOnPageLoad: false,
autoLoad:true,
model:'TopModel',
getGroupString: function(record) {
return record.get('leaf');
}
});
});
I am not able to retrieve the values for record.get('leaf') as this refers to the child model items. When I tried to print it, it prints as undefined.
How to access the child attributes? Here 'items' is listed as an array.
I tried to display the data using list as below. All the records are displayed but the problem is that it is picked as one whole item instead of separate list for each item.
var list = new Ext.List({
cls: 'big-list',
grouped : true,
emptyText: '<div>No data found</div>',
itemTpl: ['<div><tpl for="items">',
'<div>',
'{id} {text}',
'</div>',
'</tpl></div>',
],
store: this.stores,
listeners: {
itemtap: this.onListItemTap,
scope: this
}
});
Kindly help me on how to get the list items to be displayed as individual records.
you can use online json parser(http://json.parser.online.fr/) to parse json xml from parser data you easily seprate objects and arrays and you get all data which are require for you..i hope this help you