I have 3 models. User, Item and Location.
User.hasMany(Item)
Location.hasMany(Item)
Item.belongsTo(User)
Item.belongsTo(Location)
this runs, but does not create the location foreign key nor location row in locations table.
models.User.create(
{
username: 'ddavids',
email: 'hello#david.com',
password: '12345678',
items: [
{
itemName: 'Good Book',
orderDate: '2020-01-20',
locations: { locationName: 'floor' }
},
{
itemName: 'Bad Book',
orderDate: '2020-01-21',
locations: { locationName: 'shelf' }
}
]
},
{
include: [{ model: models.Item, include: [models.Location] }]
}
)
This creates the items and locations correctly but obviously not under a user.
models.Location.create(
{
locationName: 'floor',
items: [
{
itemName: 'Good Book',
orderDate: '2020-01-20',
locations: { locationName: 'floor' }
},
{
itemName: 'Bad Book',
orderDate: '2020-01-21',
locations: { locationName: 'shelf' }
}
]
},
{ include: [models.Item] }
)
What I can't figure out is if my relations are the wrong way to go about this or if its a limitation of create and I should move on or what.
My end goal will be something along the lines of.
User.hasMany(Order)
Order.belongsTo(User)
Order.hasMany(Item)
Item.belongsTo(Order)
Location.hasMany(Item)
Item.belongsTo(location)
Supplier.hasMany(Item)
Item.belongsTo(Supplier)
I am currently using create just to create some fake data for when I make changes. So if there is a better way to seed the database that would be my end goal.
Because of the association of Location and Item models is one-to-many. So you should change the
locations: { locationName: 'floor' } to location: { locationName: 'floor' }. The location of Item should not be plural. It means each item belongs to one location.
Here is the working example:
index.ts:
import { sequelize } from '../../db';
import { Model, DataTypes } from 'sequelize';
class User extends Model {}
User.init(
{
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
},
{ sequelize, modelName: 'user' },
);
class Location extends Model {}
Location.init(
{
locationName: DataTypes.STRING,
},
{ sequelize, modelName: 'location' },
);
class Item extends Model {}
Item.init(
{
itemName: DataTypes.STRING,
orderDate: DataTypes.STRING,
},
{ sequelize, modelName: 'item' },
);
User.hasMany(Item);
Location.hasMany(Item);
Item.belongsTo(User);
Item.belongsTo(Location);
(async function test() {
try {
await sequelize.sync({ force: true });
await User.create(
{
username: 'ddavids',
email: 'hello#david.com',
password: '12345678',
items: [
{
itemName: 'Good Book',
orderDate: '2020-01-20',
location: { locationName: 'floor' },
},
{
itemName: 'Bad Book',
orderDate: '2020-01-21',
location: { locationName: 'shelf' },
},
],
},
{
include: [{ model: Item, include: [Location] }],
},
);
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
After execution above code, check the data records in the database:
node-sequelize-examples=# select * from "user";
id | username | email | password
----+----------+-----------------+----------
1 | ddavids | hello#david.com | 12345678
(1 row)
node-sequelize-examples=# select * from "location";
id | locationName
----+--------------
1 | floor
2 | shelf
(2 rows)
node-sequelize-examples=# select * from "item";
id | itemName | orderDate | userId | locationId
----+-----------+------------+--------+------------
1 | Good Book | 2020-01-20 | 1 | 1
2 | Bad Book | 2020-01-21 | 1 | 2
(2 rows)
Data records inserted as expected.
sequelize version: "sequelize": "^5.21.3",
Source code: https://github.com/mrdulin/node-sequelize-examples/tree/master/src/examples/stackoverflow/59937776
Related
I am trying to update a row in a given table using the following Sequelize query:
const handleEditProfile = async (req, res) => {
let medicalconditions = req.body.medicalconditions;
let allergies = req.body.allergies;
let bloodtype = req.body.bloodtype;
let weight = req.body.weight;
let height = req.body.height;
let userId = req.body.userid;
Profile.bulkCreate(
[{userId: userId, medicalconditions: medicalconditions, allergies: allergies, bloodtype: bloodtype, weight: weight, height: height}],
{updateOnDuplicate: ['medicalconditions', 'allergies', 'bloodtype', 'weight', 'height']}
)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(400).send(err);
})
};
This is what I get in the Postgres DB:
id | medicalconditions | allergies | bloodtype | weight | height | createdAt | updatedAt | userId
----+-----------------------------+--------------------+-----------+---------+------------+----------------------------+----------------------------+--------
74 | type 2 diabetes | penicillin | AB | 114 lbs | | 2023-02-13 14:56:21.789-06 | 2023-02-13 14:56:21.789-06 | 40
90 | hyperthyroidism | none | AB | 120 lbs | 5'4" | 2023-02-17 18:08:44.503-06 | 2023-02-17 18:08:44.503-06 | 40
As you can see, I looked up the "row" by the userId. I want to update the columns mentioned in "updateOnDuplicate" where userId = 40. However, instead of updating the row with the id = 74 and userId = 40, it creates a new row with the id = 90 and userId = 40. How can I fix this without mentioning the primary id?
const User = sequelize.define('user', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
uid: {
type: DataTypes.TEXT,
allowNull: false,
unique: true
},
firstName: {
type: DataTypes.TEXT,
allowNull: false
},
lastName: {
type: DataTypes.TEXT,
allowNull: false
},
DOB: {
type: DataTypes.TEXT,
allowNull: false
}
});
const Profile = sequelize.define('profile', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
medicalconditions: {
type: DataTypes.TEXT
},
allergies: {
type: DataTypes.TEXT
},
bloodtype: {
type: DataTypes.TEXT
},
weight: {
type: DataTypes.TEXT
},
height: {
type: DataTypes.TEXT
}
});
User.hasOne(Profile);
Profile.belongsTo(User);
If you want Profile records to be unique not only by PK but by userId as well you need an unique index/constraint in Profile table on userId. That way if you don't indicate PK in objects that about to be inserted PostgreSQL will look at fields values that correspond to any unique constraint to decide whether a record with such values already exists or doesn't.
Another benefit of having the unique index on userid that you can't create two records with the same userId even if you use an individual create calls.
Goal
I have a chat bot project which based on discord and telegram. I stored discord and telegram user data and message they send in database.
Now, I want to search user and count how many users through messages they send, ex: a discord user A send a message "hello world", and a telegram user B also send a message "hello world", I search "hello", then I could get A and B user data, and count 2.
Tool
nodes.js: 16.14.2
sequelize.js: 5.22.4
postgresql: 14.5
Database tables
Users
const Users = sequelize.define("Users", {
name: DataTypes.TEXT,
userId: DataTypes.TEXT
})
Users.hasMany(models.Messages, {
as: 'discordMessages',
sourceKey: 'userId',
foreignKey: 'discordUserId'
})
Users.hasMany(models.Messages, {
as: 'telegramMessages',
sourceKey: 'userId',
foreignKey: 'telegramUserId'
})
I discriminate user from which platform by userId.
The data like:
name | userId
-------------
A | 123abc
-------------
B | 456def
-------------
C | 789ghi
A is a discord user, B is a telegram user
Messages
const Messages = sequelize.define("Messages", {
message: DataTypes.TEXT,
discordUserId: DataTypes.TEXT,
telegramUserId: DataTypes.TEXT
})
Messages.belongsTo(models.Users, {
as: 'discordMessages',
targetKey: 'userId',
foreignKey: 'discordUserId'
})
Messages.belongsTo(models.Users, {
as: 'telegramMessages',
targetKey: 'userId',
foreignKey: 'telegramUserId'
})
The relationship between Users and Messages: a user has many messages, a message only belongs to one user.
The data like:
message | discordUserId | telegramUserId
------------------------------------------------
hello world | 123abc |
------------------------------------------------
hello world | | 456def
------------------------------------------------
sorry. | 123abc |
------------------------------------------------
thank you. | | 456def
------------------------------------------------
I'm happy | | 789ghi
What I've tried
const { count, rows } = await Users.findAndCountAll({
where: {
[Op.or]: [
{ '$discordMessages.message$': { [Op.iLike]: `%${search}%` } },
{ '$telegramMessages.message$': { [Op.iLike]: `%${search}%` } }
]
},
include: [
{ model: Messages, as: 'discordMessages' },
{ model: Messages, as: 'telegramMessages' }
],
subQuery: false,
distinct: true
})
This version's code is the closest to what I want, but it's still something wrong. I can't get the right users or counts. I want to get user A and user B, then count 2, but it only return user A.
The code above return:
count: 2
rows: [
Users: {
name: 'A',
userId: '123abc'
}
]
These are some version I also tried:
const { count, rows } = await Users.findAndCountAll({
where: {
[Op.or]: [
{ '$discordMessages.message$': { [Op.iLike]: `%${search}%` } },
{ '$telegramMessages.message$': { [Op.iLike]: `%${search}%` } }
]
},
include: [
{ model: Messages, as: 'discordMessages' },
{ model: Messages, as: 'telegramMessages' }
],
subQuery: false,
distinct: true,
required: true,
distinct: true
})
const { count, rows } = await Users.findAndCountAll({
include: [
{
model: Messages,
as: 'discordMessages',
where: {
message: { [Op.iLike]: `%${search}%` }
},
separate: true
},
{
model: Messages,
as: 'telegramMessages',
where: {
message: { [Op.iLike]: `%${search}%` }
},
separate: true
}
],
subQuery: false,
distinct: true
})
The result I want:
There are all three users. After query, return two users data have message that I search and tell me two user matched I search.
count: 2
rows: [
Users: {
name: 'A',
userId: '123abc'
},
Users: {
name: 'B',
userId: '456def'
}
]
You can't get the correct counts in the same query because you have 2 JOINs in the query and that means if you have 2 telegram messages and 3 discord messages then you'll end up with 6 records in total for ONE user. You can try to exclude all attributes in both discordMessages and telegramMessages but still there is no guarantee you'll get the correct number of users.
The most correct way in case you don't need any data from both discordMessages and telegramMessages is to use a subquery in where option for User:
const { count, rows } = await Users.findAndCountAll({
where: Sequelize.where(Sequelize.literal('(SELECT COUNT(*) from "Messages" where ("Messages"."discordUserId"="User"."userId" OR "Messages"."telegramUserId"="User"."userId") AND ("Messages"."message" ILIKE $search))'), '>', '0'),
bind: {
search: `%${search}%`
}
})
Maybe this is a simple fix, but I can't seem to figure out what I'm doing wrong here.
I have a table that lists all the states
Model:
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string', minLength: 1, maxLength: 100 },
},
}
}
static get relationMappings() {
return {
users: {
relation: Model.HasManyRelation,
modelClass: User,
join: {
from: `${tableNames.state}.id`,
to: `${tableNames.user}.state_id`,
},
},
}
Migration:
await knex.schema.createTable(tableNames.state, (table) => {
table.increments().primary().notNullable()
table.string('name', 100).notNullable()
User table model:
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'integer' },
first_name: { type: 'string', minLength: 1, maxLength: 100 },
last_name: { type: 'string', minLength: 1, maxLength: 100 },
state_id: { type: 'integer', default: null },
},
}
}
static get relationMappings() {
return {
state: {
relation: Model.BelongsToOneRelation,
modelClass: State,
join: {
from: `${tableNames.user}.state_id`,
to: `${tableNames.state}.id`,
},
}
}
}
User table migration:
await knex.schema
.createTable(tableNames.user, (table) => {
table.increments().primary().notNullable()
table.string('first_name', 100).notNullable()
table.string('last_name', 100).notNullable()
table.integer('state_id').unsigned()
table
.foreign('state_id')
.references('id')
.inTable(tableNames.state)
.onDelete('SET NULL')
})
Now the issue: I want the state_id column to be nullable, as in not every user will have a state assigned to them. But when I try inserting a user with no state_id, I get this: insert or update on table \"user\" violates foreign key constraint \"user_state_id_foreign\".
two things you are doing wrong
in your json schema define your column as state_id: {type: ['integer', 'null']}
in your user migrations make table.integer('state_id').unsigned().nullable()
I want to return the has many association from this model
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define('Message', {
title: DataTypes.STRING,
message: DataTypes.STRING,
userId: DataTypes.STRING,
teacherId: DataTypes.STRING,
}, {});
Message.associate = function(models) {
Message.belongsTo(models.User, {foreignKey: 'userId'});
Message.belongsTo(models.Teacher, {foreignKey: 'teacherId'});
Message.hasMany(models.Reply, {foreignKey: 'messageId'});
};
return Message;
}
the model i want to include
module.exports = (sequelize, DataTypes) => {
const Reply = sequelize.define('Reply', {
reply: DataTypes.STRING,
messageId: DataTypes.STRING,
}, {});
Reply.associate = function(models) {
Reply.belongsTo(models.Message, {foreignKey: 'messageId'});
};
return Reply;
}
i get an error every time i run this code.
getUserMessages(req, res){
const {userId} = req.params;
Message.findAll({include:[{model:"Reply"}],where: { userId }}).then((e) =>{
res.json(e);
}).catch((error) =>{
console.log(error);
})
},
the error i get when i try to access the controller
error: column Replies.teacherId does not exist
How can i include this model on my find all query?
It seems that your models don't have correct associations. Here is a working example:
index.ts:
import { sequelize } from '../../db';
import { Model, DataTypes } from 'sequelize';
class User extends Model {}
User.init({}, { sequelize, modelName: 'users' });
class Teacher extends Model {}
Teacher.init({}, { sequelize, modelName: 'teachers' });
class Reply extends Model {}
Reply.init(
{
id: {
primaryKey: true,
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
},
},
{ sequelize, modelName: 'replies' },
);
class Message extends Model {}
Message.init(
{
title: DataTypes.STRING,
message: DataTypes.STRING,
userId: DataTypes.INTEGER,
teacherId: DataTypes.INTEGER,
},
{ sequelize, modelName: 'messages' },
);
const MessageBelongsToUser = Message.belongsTo(User, { foreignKey: 'userId' });
const MessageBelongsToTeacher = Message.belongsTo(Teacher, { foreignKey: 'teacherId' });
Message.hasMany(Reply, { foreignKey: 'messageId' });
Reply.belongsTo(Message, { foreignKey: 'messageId' });
(async function test() {
try {
await sequelize.sync({ force: true });
await Message.create(
{
title: 'messsage title',
message: 'message content',
user: {},
teacher: {},
replies: [{ id: 1 }, { id: 2 }],
},
{ include: [{ association: MessageBelongsToUser }, Reply, { association: MessageBelongsToTeacher }] },
);
const userId = 1;
const rval = await Message.findAll({ include: [{ model: Reply }], where: { userId }, raw: true });
console.log('rval: ', rval);
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
The execution result of above code:
rval: [ { id: 1,
title: 'messsage title',
message: 'message content',
userId: 1,
teacherId: 1,
'replies.id': 1,
'replies.messageId': 1 },
{ id: 1,
title: 'messsage title',
message: 'message content',
userId: 1,
teacherId: 1,
'replies.id': 2,
'replies.messageId': 1 } ]
Check the data records in the database:
node-sequelize-examples=# select * from users;
id
----
1
(1 row)
node-sequelize-examples=# select * from replies;
id | messageId
----+-----------
1 | 1
2 | 1
(2 rows)
node-sequelize-examples=# select * from messages;
id | title | message | userId | teacherId
----+----------------+-----------------+--------+-----------
1 | messsage title | message content | 1 | 1
(1 row)
node-sequelize-examples=# select * from teachers;
id
----
1
source code: https://github.com/mrdulin/node-sequelize-examples/tree/master/src/examples/stackoverflow/60479744
I have two Sequelize models that are associated with a belongsTo relationship. I would like to create an instance of user_sources when user is created but I am struggling to accomplish it.
model_user:
const User = sequelize.define('user', {
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false
}
}, {
tableName: 'users'
})
model_user_sources:
const UserSources = sequelize.define('user_sources', {
abcNews: {
type: Sequelize.BOOLEAN,
},
bbcNews: {
type: Sequelize.BOOLEAN,
}
}, {
tableName: 'user_sources'
})
UserSources.belongsTo(User)
Both models are initialized and the tables are created in the database properly. According to the Sequelize documentation I should be able to create both with association in a single query like so:
User
.create({
email: user.email,
password: user.password,
}, {
include: UserSources
})
However, only the user is created. The user_sources item does not get created in the table.
Unfortunately the documentation only shows an example of creating a parent model from a child model but not the other way around. I have tried several different methods such as using a hasOne association, adding model/association options into the include, putting data into the create method, etc. But I feel as though I am not grasping the concept properly.
Would appreciate if someone could shed some light on my problem. Thanks.
"sequelize": "^5.21.3". Here are three ways to create data records for User and UserSources model with associations. Besides, we keep adding the foreign key constraint using userId to user_sources table.
E.g.
index.js:
import { sequelize } from '../../db';
import Sequelize from 'sequelize';
const User = sequelize.define(
'user',
{
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
},
{
tableName: 'users',
},
);
const UserSources = sequelize.define(
'user_source',
{
abcNews: {
type: Sequelize.BOOLEAN,
},
bbcNews: {
type: Sequelize.BOOLEAN,
},
},
{
tableName: 'user_sources',
},
);
UserSources.belongsTo(User);
// User.UserSources = User.hasOne(UserSources);
// User.hasOne(UserSources);
(async function test() {
try {
await sequelize.sync({ force: true });
// 1. User.UserSources = User.hasOne(UserSources);
// await User.create(
// {
// email: 'example#gmail.com',
// password: '123',
// user_source: {
// abcNews: true,
// bbcNews: true,
// },
// },
// {
// include: [
// {
// association: User.UserSources,
// },
// ],
// },
// );
// 2. User.hasOne(UserSources);
// await User.create(
// {
// email: 'example#gmail.com',
// password: '123',
// user_source: {
// abcNews: true,
// bbcNews: true,
// },
// },
// {
// include: [UserSources],
// },
// );
// 3. UserSources.belongsTo(User);
await UserSources.create(
{
abcNews: true,
bbcNews: true,
user: {
email: 'example#gmail.com',
password: '123',
},
},
{
include: [User],
},
);
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
After executing the above code, check the data records in the database:
node-sequelize-examples=# select * from "users";
id | email | password
----+-------------------+----------
1 | example#gmail.com | 123
(1 row)
node-sequelize-examples=# select * from "user_sources";
id | abcNews | bbcNews | userId
----+---------+---------+--------
1 | t | t | 1
(1 row)
The data records are created as expected.