Node.js Sequelize ManyToMany relations producing incorrect SQL - javascript

I'm having a problem with sequelize ManyToMany relations.
Here are my models...
var db = {
players: sequelize.define('players', {
name: Sequelize.STRING
}),
teams: sequelize.define('teams', {
name: Sequelize.STRING
}),
init: function() {
this.players.hasMany(this.teams, {joinTableName: 'teams_has_players'});
this.teams.hasMany(this.players, {joinTableName: 'teams_has_players'});
this.players.sync();
this.teams.sync();
}
};
Here's the find
db.players.findAll({
where: {team_id: 1},
include: ['teams']
}).success(function(results) {
// print the results
});
The above find will produce the following SQL:
SELECT
players . *,
teams.name AS `teams.name`,
teams.id AS `teams.id`
FROM
players
LEFT OUTER JOIN
teams_has_players ON teams_has_players.player_id = players.id
LEFT OUTER JOIN
teams ON teams.id = teams_has_players.team_id
WHERE
players.team_id = '1';
What appears to be wrong with this is that the WHERE statement should be WHERE teams.team_id = '1'
Where am I going wrong with this?
Thanks in advance

Hmm actually everything looks quite OK-ish. db.players.findAll with where: { team_id: 1 } should create a query with WHERE players.team_id = '1'. That's perfectly expected. Also, you teams won't have a team_id but an id instead. However, there is a good chance that include is broken atm.

Related

Sequelize - Delete association records from parent instance

I am trying to find a way to remove rows from the DB through the parent model (menu) that has many children (foods). I only want to delete certain rows though, not all.
Menu.js
...
Menu.hasMany(models.Food, {
as: 'foods',
foreignKey: 'menuId',
sourceKey: 'id'
});
...
In my controller I have the following to try and delete certain foods off the menu.
...
const result = await menu.destroyFoods({
where: {
name: ['Pasta', 'Pizza']
}
});
...
I have also tried singular destroyFood as well. For both I am getting destoryFood/destoryFoods is not a function. Is there any easy way to do this from the instance of menu? New to sequelize, would love some help. Thanks.
Thanks
You can use menu.removeFoods() and menu.removeFood() - see Special methods/mixins added to instances: Foo.hasMany(Bar) for more information.
You will also need to use the Op.in query operator to specify multiple values for Food.name.
const { Op } = require('sequelize');
const result = await menu.removeFoods({
where: {
name: {
[Op.in]: ['Pasta', 'Pizza'],
}
}
});
This is the equivalent of calling Food.destroy() where the menuId is equal to the menu.id from the earlier result.
const results = await Food.destroy({
where: {
menuId: menu.id,
name: {
[Op.in]: ['Pasta', 'Pizza'],
},
},
});

Possible to WHERE on nested includes in Sequelize?

I've got a problem that I've been stuck on, to no avail - seemingly similar in nature to Where condition for joined table in Sequelize ORM, except that I'd like to query on a previous join. Perhaps code will explain my problem. Happy to provide any extra info.
Models:
A.hasMany(B);
B.belongsTo(A);
B.hasMany(C);
C.belongsTo(B);
This is what I'd like to be able to achieve with Sequelize:
SELECT *
FROM `A`AS `A`
LEFT OUTER JOIN `B` AS `B` ON `A`.`id` = `B`.`a_id`
LEFT OUTER JOIN `C` AS `B->C` ON `B`.`id` = `B->C`.`b_id`
AND (`B`.`b_columnName` = `B->C`.`c_columnName`);
This is how I imagine this working: (instead it will create a raw query (2 raw queries, for A-B/C) with AND ( `C`.`columnName` = '$B.columnName$')) on the join (second arg is a string). Have tried sequelize.col, sequelize.where(sequelize.col..., etc..)
A.findOne({
where: { id: myId },
include: [{
model: B,
include: [{
model: C,
where: { $C.c_columnName$: $B.b_columnName$ }
}]
}]
});
Use the Op.col query operator to find columns that match other columns in your query. If you are only joining a single table you can pass an object instead of an array to make it more concise.
const Op = Sequelize.Op;
const result = await A.findOne({
include: {
model: B,
include: {
model: C,
where: {
c_columnName: {
[Op.col]: 'B.b_columnName',
},
}
},
},
});

Mongoose virtuals are not added to the result

I'm building a quiz editor where rounds contain questions and questions can be in multiple rounds. Therefor I have the following Schemes:
var roundSchema = Schema({
name: String
});
var questionSchema = Schema({
question: String,
parentRounds: [{
roundId: { type: Schema.Types.ObjectId, ref: 'Round'},
isOwner: Boolean
}]
});
What I want is to query a round, but also list all questions related to that round.
Therefor I created the following virtual on roundSchema:
roundSchema.virtual('questions', {
ref : 'Question',
localField : '_id',
foreignField : 'parentRounds.roundId'
});
Further instantiating the Round and Question model and querying a Round results in an object without questions:
var Round = mongoose.model('Round', roundSchema, 'rounds');
var Question = mongoose.model('Question', questionSchema, 'questions');
Round.findById('5ba117e887f66908ae87aa56').populate('questions').exec((err, rounds) => {
if(err) return console.log(err);
console.log(rounds);
process.exit();
});
Result:
Mongoose: rounds.findOne({ _id: ObjectId("5ba117e887f66908ae87aa56") }, { projection: {} })
Mongoose: questions.find({ 'parentRounds.roundId': { '$in': [ ObjectId("5ba117e887f66908ae87aa56") ] } }, { projection: {} })
{ _id: 5ba117e887f66908ae87aa56, __v: 0, name: 'Test Roundname' }
As you can see, I have debugging turned on, which shows me the mongo queries. It seems like the second one is the one used to fill up the virtual field.
Executing the same query using Mongohub DOES result in a question:
So why doesn't Mongoose show that questions array I'm expecting?
I've also tried the same example with just one parentRound and no sub-objects, but that also doesn't work.
Found the answer myself...
Apparently, I have to use
console.log(rounds.toJSON({virtuals: true}));
instead of
console.log(rounds);
Why would Mongoose do such a devil thing? :(

Normalizr - is it a way to generate IDs for non-ids entity model?

I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256

Custom or override join in Sequelize.js

I need to create a custom join condition using Sequelize.js with MSSQL. Specifically, I need to join TableB based on a COALESCE value from columns in TableA and TableB and end up with a join condition like this:
LEFT OUTER JOIN [TableB]
ON [TableB].[ColumnB] = COALESCE (
[TableC].[ColumnC],
[TableA].[ColumnA]
)
I'd settle for an OR clause in my join:
LEFT OUTER JOIN [TableB]
ON [TableB].[ColumnB] = [TableA].[ColumnA]
OR [TableB].[ColumnB] = [TableC].[ColumnC]
I read that you can achieve behaviour like this by including required: false in your scope definition. As you can see, I've plastered my scope with it attempting to get this to work.
The best I could get is this (note the AND clause):
LEFT OUTER JOIN [TableB]
ON [TableB].[ColumnB] = [TableA].[ColumnA]
AND [TableB].[ColumnB] = COALESCE (
[TableC].[ColumnC],
[TableA].[ColumnA]
)
If I were using MySQL, I think I could simply use the COALESCE value from the SELECT in the JOIN and be good to go but in my prior research read that it was required to recalculate the value.
I've included a stripped down model definition for TableA:
export default (sequelize, DataTypes) => sequelize.define('TableA',
{
// Attributes omitted..
},
{
classMethods: {
associate ({
TableB,
TableC
}) {
this.belongsTo(TableB, {
foreignKey: 'ColumnA',
targetKey: 'ColumnB'
});
this.belongsTo(TableC, {
foreignKey: 'ColumnA',
targetKey: 'ColumnC'
});
},
attachScope ({
TableB,
TableC
}) {
this.addScope('defaultScope', {
attributes: [
...Object.keys(this.attributes),
[
sequelize.fn(
'COALESCE',
sequelize.col('[TableC].[ColumnC]'),
sequelize.col('[TableA].[ColumnA]')
),
'ColumnA'
]
],
include: [
{
model: TableB,
where: {
ColumnB: sequelize.fn(
'COALESCE',
sequelize.col('[TableC].[ColumnC]'),
sequelize.col('[TableA].[ColumnA]')
)
},
required: false
},
{
model: TableC,
required: false
}
],
required: false
}, { override: true });
}
}
}
);
Any assistance would be greatly appreciated, and if any additional information is required please let me know.
Note: I'm working with a legacy database and unfortunately cannot change the data structure.
Hi I had the same issue and finally I solved using a SQL pure query.
Example:
const query = `SELECT s.*, us.UserId \
FROM UserSections AS us \
RIGHT JOIN Sections AS s ON (s.id = us.SectionId) \
WHERE s.parentId = ${sectionId}`;
return db.query(query, { type: db.QueryTypes.SELECT });
The problem is that this function will return a IMyType instead of IMyTypeInstance
Can it works for you??

Categories

Resources