So I have two tables: Class and Course. Each Class has a course_id that defines what type of course it is on the Course table. Here are my models:
const Class = sequelize.define('class', {
class_date: Sequelize.DATE,
begin_time: Sequelize.TIME,
end_time: Sequelize.TIME,
max_capacity: Sequelize.INTEGER,
is_published: Sequelize.BOOLEAN,
training_facility_id: Sequelize.INTEGER,
state_id: Sequelize.INTEGER,
registration_deadline: Sequelize.DATE,
course_id: Sequelize.INTEGER,
is_report_60_generated: Sequelize.BOOLEAN,
completed_by_user_id: Sequelize.INTEGER
}, {
timestamps: false,
freezeTableName: true
})
const Course = sequelize.define('course', {
code: Sequelize.STRING,
name: Sequelize.STRING
}, {
timestamps: false,
freezeTableName: true
})
When I hit the classes endpoint, I get a array of classes. How would I set up the query to respond with the course name and code for each class instead of just the course_id?
You will need to create a relationship between the Class and Course models. If you specify the underscored: true option in the Model definition it won't use camel case and will automatically create the Class.course_id column, so you don't need to define it (the same probably goes for completed_by_user_id).
const Class = sequelize.define('class', {
class_date: Sequelize.DATE,
begin_time: Sequelize.TIME,
end_time: Sequelize.TIME,
max_capacity: Sequelize.INTEGER,
is_published: Sequelize.BOOLEAN,
training_facility_id: Sequelize.INTEGER,
state_id: Sequelize.INTEGER,
registration_deadline: Sequelize.DATE,
// this will be auto-created by the relationship
// course_id: Sequelize.INTEGER,
is_report_60_generated: Sequelize.BOOLEAN,
// you probably want a relationship here as well
completed_by_user_id: Sequelize.INTEGER
}, {
timestamps: false,
freezeTableName: true,
// use underscored names
underscored: true,
})
const Course = sequelize.define('course', {
code: Sequelize.STRING,
name: Sequelize.STRING
}, {
timestamps: false,
freezeTableName: true,
underscored: true,
})
Based on your data model it seems as though each Class will require an associated Course with different attributes like start time, etc, so you will tell Sequelize that each Class belongsTo() a Course.
// tell Sequelize that once course will be assigned to many classes, and it is required (not null)
Class.belongsTo(Course, { foreignKey: { allowNull: false } })
Once they are related you can use the include option value to specify a model to join to your primary query. If you use as in the relationship you must specify it here as well.
// do a joined query using "include"
Class.findAll({
include: [
{
model: Course
}
]
})
Related
Is this possible or must the entire project be nuked and redone?
Example: I want to add a "breedId" column to a dogs table to reference model "Breeds"
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Dog', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING(50),
allowNull: false
},
description: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Dog');
}
};
Since you appear to be using migrations, you will have a pretty easy time adding onto your tables.
Create another migration, and use queryInterface.addColumn(tableName: string, columnName: string, options: object) for the up, and queryInterface.removeColumn(tableName: string, columnName: string) for the down.
If you are using sequeilze.sync() to build your tables from the models, your options are worse. You either need to pass {force: true} into the .sync() method, which will drop the current tables before rebuilding them with the attributes in your models. Or, to preserve data you can omit {force: true} by manually adding the columns and foreign key constraints to the database, and add the corresponding attributes to the models.
So i have this sequelize model of an Organization:
module.exports = (sequelize, Sequelize) => {
const Organization = sequelize.define("organization", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
organizationName: {
type: Sequelize.STRING,
allowNull: false,
notEmpty: true,
unique: {
args: 'username',
msg: 'This username is already taken!'
}
},
emailAddress: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail: true
},
unique: {
args: 'email',
msg: 'The email is already taken!'
}
},
physicalAddress: {
type: Sequelize.STRING,
allowNull: true
},
city: {
type: Sequelize.STRING,
allowNull: false
},
country: {
type: Sequelize.STRING,
allowNull: false
},
phone: {
type: Sequelize.INTEGER,
allowNull: true
},
websiteAddress: {
type: Sequelize.STRING,
allowNull: true
}
}, {
timestamps: false,
underscored: true,
freezeTableName: true
});
return Organization;
};
When the user registers with a new Organization i want to make sure that the username entered is unique.
My question is... Is my implementation enough? Or should i be doing a organization.find before insert?
It does work but I'm worried.
I am aware that with the above method the id gets auto incremented even if validation fails. But are there any other issues?
You are relying on the DB kicking out an error if the name is not unique. This should be a last resort really and would recommend you query your table before you attempt an insert. However, you could look at the upsert style operations like findOrCreate
Additional Thought:
It would be less strain on your db, more scalable, and a better experience for your users, if you kept a cached list of existing usernames and searched that before you attempt the insert - as part of your validation process. Ideally, this would be called from an ajax query when the user has entered the organizational name.
UPDATE on some catching ideas:
Redis is a great start. It's widely adopted and implemented at all major providers like AWS, Heroku, Azure, etc. It's simple to provision and effortless to set up locally. Having a central, shared cache allows you to share data between your web/api instances, will be substantially faster to query, and takes the strain off your DB when cached data will suffice. I like the ioredis driver.
I've created a database with two tables, Users and Points. A user can have many points and a point stores the ID of the user who sent it and the user who received it. I am trying to query for a table grouped by user showing the sum of the amounts of all their points, which is working querying raw in postgresql but not in sequelize.
Working in postgresql:
Creating the models with sequelize:
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
telegram_id: {
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: "users",
sequelize: sequelize, // this bit is important
}
);
Point.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
amount: {
type: DataTypes.INTEGER,
allowNull: false,
},
to_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
from_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
tableName: "points",
sequelize: sequelize, // this bit is important
}
);
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "to_id",
as: "recievedPoints", // this determines the name in `associations`!
});
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "from_id",
as: "sentPoints", // this determines the name in `associations`!
});
Point.belongsTo(User, {
foreignKey: "to_id",
targetKey: "telegram_id",
as: "toUser",
});
Point.belongsTo(User, {
foreignKey: "from_id",
targetKey: "telegram_id",
as: "fromUser",
});
Attempting to make same query with sequelize:
const points = await Point.findAll({
attributes: [
"users.name",
"points.to_id",
[Sequelize.fn("SUM", Sequelize.col("points.amount")), "points.amount"],
],
include: ["toUser"],
group: ["users.name", "points.to_id"],
});
Resulting error:
SequelizeDatabaseError: invalid reference to FROM-clause entry for table "users"
SQL generated by sequelize:
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt" FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";
RAW QUERY :
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt"
FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";
As per your raw query :
Change "users" to "toUser" every where
Change "points" to "Point" every where , like this :
const points = await Point.findAll({
attributes: [
"toUser.name",
"Point.to_id",
[Sequelize.fn("SUM", Sequelize.col("Point.amount")), "Point.amount"],
],
include: ["toUser"],
group: ["toUser.name", "Point.to_id"],
});
I am new developer and am trying to teach myself Sequelize and mysql with some little test projects. What I have right now is a little RPG team strength analyzer. I have a SQL table of Units, which has schema (id, name, elementOne, elementTwo) - integer, string, string, string.
For now, the elementOne and ElementTwo tables are both the same 18 string values because I couldn't figure out how to set up the Sequelize query with foreign keys refs to the same table (e.g. just 'elements').
Adding to the Unit table works fine on a local server, but breaks on Heroku ONLY when trying to add a third unit with the following error:
Error was: { SequelizeForeignKeyConstraintError: Cannot add or update a child
row: a foreign key constraint fails (`heroku_f4daeab1e260595`.`units`,
CONSTRAINT `units_ibfk_1` FOREIGN KEY (`id`) REFERENCES `elementtwos` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE)
Here are all the tables and the relationship declarations.
const Unit = sequelize.define('unit', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
},
image: {
type: Sequelize.STRING,
allowNull: true,
unique: false
},
elementOne: {
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: Element,
key: 'id'
}
},
elementTwo: {
type: Sequelize.INTEGER,
allowNull: true,
defaultValue: 10001,
references: {
model: ElementTwo,
key: 'id'
}
}
});
const Element = sequelize.define('element', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
}
});
const ElementTwo = sequelize.define('elementtwo', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: false
}
});
After these are all loaded, I set up the following:
Unit.belongsTo(Element, {foreignKey: 'elementOne'});
Unit.belongsTo(ElementTwo, {foreignKey: 'elementTwo'});
ElementTwo.hasMany(Unit, {foreignKey: 'id'});
Element.hasMany(Unit, {foreignKey: 'id'});
And this is the query that Sequelize is doing (in a Unit.create({...}):
INSERT INTO `units`
(`id`,`name`,`image`,`elementOne`,`elementTwo`,`createdAt`,`updatedAt`) VALUES
(DEFAULT,'raichu','http://longimgurl.png',13,10001,'2017-06-14
12:57:54','2017-06-14 12:57:54');
If anyone can offer any advice it would be greatly appreciated.
this is mysql error and because you define foreign key constraint on you table and try to insert unavailable value in fk field that does not exit in target table,
check element and elementTwo table and make sure this values are available
Consider the following models:
var User = sequelize.define('User', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
email:{
type: Datatypes.STRING,
unique: {
msg: 'Email Taken'
},
validate: {
isEmail: true
}
}
});
var Location= sequelize.define('Location', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
address: type: Datatypes.STRING
});
Location.belongsToMany(User, {through: 'UserLocation'});
User.belongsToMany(Location, {through: 'UserLocation'});
Is there a way to query the UserLocation table for a specific UserId and get the corresponding Locations. Something like:
SELECT * FROM Locations AS l INNER JOIN UserLocation AS ul ON ul.LocationId = l._id WHERE ul.UserId = 8
From what I can find you can do something similar to:
Location.findAll({
include: [{
model: User,
where: {
_id: req.user._id
}
}]
}).then( loc => {
console.log(loc);
});
However, this returns the Locations, UserLocation junctions, and User which it is joining the User table when I do not need any user information and I just need the Locations for that user. What I have done is working, however, the query against the junction table is prefered instead of the lookup on the User table.
I hope this is clear. Thanks in advance.
Edit
I actually ended up implementing this in a different way. However, I am still going to leave this as a question because this should be possible.
declaring junction table as separate class, something like this
var UserLocation = sequelize.define('UserLocation', {
//you can define additional junction props here
});
User.belongsToMany(Location, {through: 'UserLocation', foreignKey: 'user_id'});
Location.belongsToMany(User, {through: 'UserLocation', foreignKey: 'location_id'});
then you can query junction table same as any other model.