I have some application. There is an Character, which can belong to many group. A group can have many characters and also many ranks. Each Character have specified rank. Ranks differs across the groups.
Character model:
module.exports = (sequelize, DataTypes) => {
let Character = sequelize.define('Character', {
/* attributes */
}, {});
Character.associate = (models) => {
Character.hasMany(models.outfit, { foreignKey: 'owner' });
Character.belongsToMany(models.group, { through: models.groupmember });
};
return Character;
};
Group model:
module.exports = (sequelize, DataTypes) => {
let Group = sequelize.define('Group', {
/* attributes */
}, {});
Group.associate = function (models) {
Group.belongsToMany(models.character, { through: models.groupmember, as: 'members' });
Group.hasMany(models.grouprank);
};
return Group;
};
GroupMember (junction table) model:
module.exports = (sequelize, DataTypes) => {
let GroupMember = sequelize.define('GroupMember', {
/* groupId, characterId are generated by sequelize */
rankId: DataTypes.INTEGER
}, {});
GroupMember.associate = function (models) {
GroupMember.belongsTo(models.grouprank, { foreignKey: 'rankId', targetKey: 'id' });
};
return GroupMember;
};
Group rank model: (doesn't matter much in my question)
module.exports = (sequelize, DataTypes) => {
let GroupRank = sequelize.define('GroupRank', {
// atributes
}, {});
GroupRank.associate = function (models) {
GroupRank.belongsTo(models.group);
GroupRank.hasMany(models.groupmember, { foreignKey: 'rankId' });
};
return GroupRank;
};
I have added rankId column to junction table and I have problem to retrieve Character with all its groups included and also the rank which it have.
My current code, which returns the Character, the groups to which he belongst to, and also ranks, but the ALL ranks which belongs to the group. And I want just the rank with ID which is specified by rankId.
database.character.findById(characterId, {
include: [{
model: database.group,
through: database.groupmember,
include: {
model: database.grouprank,
/* where: { id: '$GroupMember.rankId$' } */
}
}]
}).then(result => {
});
Yep. I know it looks weird little, it lacks CamelCase but however my problem is about something else. I tried to query with where attribute, but Sequelize is parsing the string, so I can't put there column name. I hope the question is understable enough. Thank you for your time!
I think below code finally worked for me. http://docs.sequelizejs.com/class/lib/sequelize.js~Sequelize.html#static-method-literal
database.character.findById(characterId, {
include: [{
model: database.group,
through: database.groupmember,
include: {
model: database.grouprank,
where: database.Sequelize.literal('`Groups->GroupRanks`.`id` = `Groups->GroupMember`.`rankId`')
}
}]
})
edit: Finally I decided to rewrite models as stated here -> FindAll with includes involving a complicated many-to-(many-to-many) relationship (sequelizejs)
Related
I am trying to use Sequelize and my brain must have been burnt from the whole project and now I cannot even join tables lol so here we go:
the task is I have two model that creates two tables and another model empty (so an empty table) I would like to join the two table in the third one and after in another statement I would like to count how many users(example) has the first column if there are two user with the same name means that is only one so will count just as one.
module.exports = (sequelize, Sequelize) => {
const Gig = sequelize.define("gig", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig;
};
module.exports = (sequelize, Sequelize) => {
const Gig1 = sequelize.define("gig1", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig1;
};
module.exports = (sequelize, Sequelize) => {
const Gig2 = sequelize.define("gig2", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig2;
};
this is in another file
router.get("/", (req, res) => {
db.Gig.findAll({here the magic should happen thanks guys})
.then((users) => {
console.log(users);
res.sendStatus(200);
})
.catch((err) => {
console.log(err);
});
});
so Gig+Gig1 = Gig2 then count the user in a (so give me the sum without clones)
I found this article written below that is similar to what I am looking for but adding the result to Gig2 that is empty and then do the calcs.
*
UNION ALL and an exclusion join
One way to make UNION include only the duplicates I want is to use an exclusion join to eliminate anything from the second result that is already included in the first, like this:
select * from apples as a
left outer join oranges as o on a.price = o.price
union all
select * from apples as a
right outer join oranges as o on a.price = o.price
where a.price is null;
This handles duplicate rows correctly and doesn’t include anything it shouldn’t. It’s necessary to use UNION ALL instead of plain UNION, which would eliminate the duplicates I want to keep. This may be significantly more efficient on large result sets, since there’s no need to sort and remove duplicates.
I am trying to write a query with that would roughly do:
SELECT Challenge.id, Challenge.name, count(AcceptedChallenge.userId) AS attempts, count(a.met) AS met, count(b.active) AS active, count(c.id) AS WHOLE
FROM Challange
LEFT JOIN AcceptedChallenge ON Challenge.id = AcceptedChallenge.challengeId,
LEFT JOIN AcceptedChallenge AS a ON Challenge.id = a.challengeId
LEFT JOIN AcceptedChallenge AS b ON Challenge.id = b.challengeId
LEFT JOIN AcceptedChallenge AS c ON Challenge.id = c.challengeId
WHERE a.met = true
AND b.userId = id and b.active = true
AND c.userId = id;
Tried multiple versions, including the below:
const Sequelize = require('sequelize');
const ChallengeController = async ({ user: { id } }) => {
const challenges = await Challenge
.findAll({
attributes: {
include: [[Sequelize.fn('count', Sequelize.col('AcceptedChallenges.userId')), 'attempts'],
[Sequelize.fn('count', Sequelize.col('a.met')), 'met']]
},
include: [{
model: AcceptedChallenge, attributes: [],
required: false,
}, {
model: AcceptedChallenge, attributes: [],
as: 'a',
where: { userId: id, met: true },
required: false,
}],
group: ['Challenge.id']
})
.catch((e) => {
console.log("error:", e);
throw new HTTP404Error('Challenges not found');
});
return challenges;
};
It is not recognizing my associations. Please advise. The version here results in: SequelizeEagerLoadingError: AcceptedChallenge is associated to Challenge using an alias. You've included an alias (a), but it does not match the alias(es) defined in your association (AcceptedChallenges).
When including the AcceptedChallenge model just once, it calculates attempts just fine. I am perplexed as to how I could do the include/JOIN multiple times to get the result, which I need from a single SQL request.
This works for me when the association is repeated, once for each alias needed in your query (example below, but obviously your association may be different).
ChallengeController.hasMany(AcceptedChallenge, {as: 'a', foreignKey: 'challengeId'});
ChallengeController.hasMany(AcceptedChallenge, {as: 'b', foreignKey: 'challengeId'});
ChallengeController.hasMany(AcceptedChallenge, {as: 'c', foreignKey: 'challengeId'});
Are you doing something similar?
I am using Sequelize (new to ORM's) and currently have three tables: card and tags, that have a many-to-many relationship that is established with the third table, card_tags. My question is two fold:
How do I seed data across associations?
How do I create new data (i.e. an API function) that establishes new data across associated data bases?
Please see the below for my models and let me know if I'm leaving anything out. Thanks!
cards model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Card = sequelize.define('card', {
title: {
type: DataTypes.STRING,
allowNull: false
},
link: {
type: DataTypes.STRING,
allowNull: false
}
});
Card.associate = (models) => {
Card.belongsToMany(models.tag, { through: 'card_tag', as: 'tag' });
};
return Card;
};
tags model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Tag = sequelize.define('tag', {
title: {
type: DataTypes.STRING,
allowNull: false
}
});
Tag.associate = (models) => {
Tag.belongsToMany(models.card, { through: 'card_tag', as: 'card'});
};
return Tag;
};
card_tags model:
'use strict';
module.exports = function(sequelize, DataTypes) {
const CardTag = sequelize.define('card_tag', {
cardId: DataTypes.INTEGER,
tagId: DataTypes.INTEGER
});
return CardTag;
};
The simplest and quickest way to do is to to insert bulk data in each relation.
You can write sequelize queries to bulkInsert keeping in mind that primary key for each foriegn key is created first.
You can use faker npm module to generate random data.
I have a feathers api set up using feathers-sequelize to persist to a MySQL database.
I've got the datamodel set up and can see the relevant tables are created.
const Sequelize = require('sequelize');
module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const orders = sequelizeClient.define('orders', {
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV4
}, {
classMethods: {
associate (models) {
this.belongsToMany(models.users, {through: "offers", foreignKey: "orderId", otherKey: "userId"});
}
}
});
return orders;
};
How do I actually create an association of offers? I've tried something like this:
const orders = hook.app.service("orders");
order.offers.push(user.id);
orders.patch(order.id, order);
but it doesn't seem to have any effect
You shouldn't need to patch with a many-to-many. Simply use the Sequelize association method:
order.addUser(user.id)
To return the association you might need to do an order.reaload() in your after hook.
I have category that can have child categories
And when I'm doing findAll I want to include all of those nested, but I don't know the depth.
var includeCondition = {
include: [
{
model: models.categories,
as:'subcategory', nested: true
}]
};
models.categories.findAll(includeCondition)
.then(function (categories) {
resolve(categories);
})
.catch(function (err) {
reject(err);
})
});
The result brings me only one level nested include.
[
{
dataValues:{
},
subcategory:{
model:{
dataValues:{
}
// no subcategory here
}
}
}
]
Can I somehow make sequalize include those nested subcategories ?
There are few solutions if found for this
first one is more complicated but will give better performance:
This one is about implementing hierarchical data structure in MySQL
I like the guide here
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
The one that is named The Nested Set Model.
The second solution that I actually implemented by myself is recursive expanding, this one uses lots of mysql requests and I believe can be improved, but it's a fast one and works well. The thing is to use for each category function like this
var expandSubcategories = function (category) {
return new promise(function (resolve, reject) {
category.getSubcategories().then(function (subcategories) {
//if has subcategories expand recursively inner subcategories
if (subcategories && subcategories.length > 0) {
var expandPromises = [];
_.each(subcategories, function (subcategory) {
expandPromises.push(expandSubcategories(subcategory));
});
promise.all(expandPromises).then(function (expandedCategories) {
category.subcategories = [];
_.each(expandedCategories, function (expandedCategory) {
category.subcategories.push(expandedCategory);
}, this);
//return self with expanded inner
resolve(category);
});
} else {
//if has no subcategories return self
resolve(category);
}
});
});
};
So it's going through the categories and expanding them recursively.
Maybe this will help someone as well.
This is ihoryam's answer adapted to ES6, using async/await, arrow functions () => and Sequelize ORM to fetch the data, and not using Lodash.
const getSubCategoriesRecursive = async (category) => {
let subCategories = await models.category.findAll({
where: {
parentId: category.id
},
raw : true
});
if (subCategories.length > 0) {
const promises = [];
subCategories.forEach(category => {
promises.push(getSubCategoriesRecursive(category));
});
category['subCategories'] = await Promise.all(promises);
}
else category['subCategories'] = [];
return category;
};
Async functions returning promises, you do not need to precise return new promise(...)
There is a node module which handle it : sequelize-hierarchy
It adds column parentId and hierarchyLevel to your table.
As an example, this is what I did to order employees skills in a tree.
Skills could be "Macro" -> "Excel" -> "Office" -> "Computer"
database.js:
const Sequelize = require('sequelize');
require('sequelize-hierarchy')(Sequelize);
const sequelize = new Sequelize("stackoverflow", null, null, {
dialect: "sqlite",
storage: "database.db"
});
sequelize.sync().then(() => {console.log("Database ready");});
module.exports = sequelize;
skill.js:
module.exports = (sequelize, DataTypes) => {
const Skill = sequelize.define("skill", {
name: DataTypes.STRING,
});
Skill.isHierarchy();
return Skill;
};
Then in your controller:
Skill.findAll().then(skills => {
res.send(skills); // Return a list
});
Skill.findAll({ hierarchy: true }).then(skills => {
res.send(skills); // Return a tree
});
Sequelize currently has no support for common table expressions and recursive CTEs. Adding the ability to include a CTE into the find* family of methods would allow find* to perform recursive queries.
Here is the link for examples.
Common Table Expressions and Recursive Queries
Suppose you have 5 different models A, B, C, D, E and A is associated with B, B with C and so on.
So while fetching data for A you can get the all the nested subcategory hierarchy by using
include: [{ all: true, nested: true }]
Example:
A.findAll(where:{// add conditions}, { include: [{ all: true, nested: true }]});