Associating a reference table in sequelize - mysql

I have a couple of tables I'm trying to associate in Sequelize -- a jobaids_sections_messages table containing messages a user enters, and a jobaids_sections_messages_levels table which is a static reference used by the messages table. They're set up like so
this.jobaidMessage = sequelize.define('jobaids_sections_messages', {
message: Sequelize.STRING,
attuid: Sequelize.STRING,
level: Sequelize.INTEGER
}, {
paranoid: true
});
this.jobaidMessageLevel = sequelize.define('jobaids_sections_messages_levels', {
name: Sequelize.STRING
}, {
timestamps: false
});
The jobaids_sections_messages_levels table is set up like so:
| id | name |
| --- | -------- |
| 1 | Critical |
| 2 | Major |
| 3 | Warning |
| 4 | Info |
I want to make is so that when I create a new message, I can pass the level in as a key to the jobaids_sections_messages_levels table, and upon retrieving a message, I get the level back as
{
...
level: {
id: 2,
name: 'Major'
}
}
How should I set my associations up here? So far, I have
this.jobaidMessageLevel.belongsTo(this.jobaidMessage, {
foreignKey: 'level'
});
Though I'm not sure about the reversal of this association. Would it be a "many-to-one" relationship of some sorts?
Thank you!

Your message has a single level and technically your levels can have many messages. So simply stating that your message hasOne level will do the association needed. Then when you pull down a message and include the level, it will come back.
this.jobaidMessage.hasOne(this.jobaidMessageLevel, {
foreignKey: 'levelId'
});

Related

How to properly make a table association using hasOne, belongsTo using Sequelize and express js

I am working on expre6ssjs with sequelize version 6.
I have User and Message and I need to make one to many relationship between them.
here is what I have so far
import { Sequelize, DataTypes } from "sequelize";
const sequelize = new Sequelize(
'messagenger',
'mysql',
'password',
{
host: 'localhost',
dialect: 'mysql'
}
);
sequelize.authenticate().then(() => {
console.log('Connection has been established successfully.');
}).catch((error) => {
console.error('Unable to connect to the database: ', error);
});
const User = sequelize.define("users", {
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Message = sequelize.define("messages", {
text: {
type: DataTypes.STRING,
allowNull: false
},
});
sequelize.sync({ force: true }).then(() => {
// User.hasMany(Message, {
// foreignKey: "user_id",
// as: "messages"
// });
// Message.belongsTo(User, {
// foreignKey: "user_id",
// as: "users"
// });
// Message.belongsTo(User, {
// foreignKey: "userId",
// as: "user"
// })
Message.belongsTo(User);
User.hasMany(Message);
}).catch((error) => {
console.error('Unable to create table : ', error);
});
this is my messages table.
mysql> describe messages;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| text | varchar(255) | NO | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
I have tried a couple of versions as you can see in the comments. but nothing worked.
https://sebhastian.com/sequelize-hasone/
I was following this tutorial and this does not look complicated at all.
By the way, I have been searching for the answer for a while now. And I know people also use a different approach such as referenced in the table definition.
But my goal is to use hasMany, belongsTo, these methods as they are very explicit.
Any advice will help me a lot.
Thank you in advance.
You need to define all models and associations BEFORE calling sync in order to Sequelize to know all about what tables and foreign key fields and foreign key constraints it needs to create.
const User = sequelize.define("users", {
name: {
type: DataTypes.STRING,
allowNull: false
}
});
const Message = sequelize.define("messages", {
text: {
type: DataTypes.STRING,
allowNull: false
},
});
Message.belongsTo(User);
User.hasMany(Message);
sequelize.sync({ force: true }).then(() => {
console.info('Model synchronization completed');
}).catch((error) => {
console.error('Unable to create table : ', error);
});

Expanding a record with unknown keys in Power Query

I am working with a nested json file. The issue is that the keys of the nested json are dates and their value is not known beforehand. Therefore I am unable to apply expandRecordColumn method on it.
Each row has a unique refId and looks like this
{
"refId" : "XYZ",
"snapshotIndexes" : {
"19-07-2021" : {
url: "abc1",
value: "123"
},
"20-07-2021" : {
url: "abc2",
value: "567"
}
}
}
I finally want a table with these columns,
refid | date | url | value
XYZ | 19-7-2021 | abc1 | 123
XYZ | 20-7-2021 | abc2 | 567
PQR | 7-5-2021 | srt | 999
In the new table, refId and date will together make a unique entry.
This is powerBi snapshot
Records
I was able to solve it using Record.ToTable on each row to convert from record to table and then applying ExpandTableColumn
let
Source = DocumentDB.Contents("sourceurl"),
Source = Source{[id="dbid"]}[Collections],
SourceTable= Source{[db_id="dbid",id="PartnerOfferSnapshots"]}[Documents],
ExpandedDocument = Table.ExpandRecordColumn(SourceTable, "Document", {"refId", "snapshotIndexes"}, {"Document.refId", "Document.snapshotIndexes"}),
TransformColumns = Table.TransformColumns(ExpandedDocument,{"Document.snapshotIndexes", each Table.ExpandRecordColumn(Record.ToTable(_), "Value", {"url","id","images"}, {"url","id","images"})}),
ExpandedTable = Table.ExpandTableColumn(TransformColumns, "Document.snapshotIndexes", {"Name","url","id","images"}, {"Document.dates","Document.url","Document.id","Document.images"})
in
ExpandedTable

TypeScript error when using Op.between in Sequelize with Dates

I want to find all records in a MySql table which was created within a certain range of date.
So I wrote:
import { Sequelize, Model, DataTypes, Op } from 'sequelize';
const sequelize = new Sequelize({
// some db connection config
dialect: 'mysql'
})
class Patient extends Model {
public guid!: number;
public name!: string;
public recordState: number = 0;
public createdAt?: Date;
public updatedAt?: Date
}
Patient.init({
guid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
name: { type: DataTypes.STRING, allowNull: false },
recordState: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
}, {
sequelize,
modelName: 'Patient',
timestamps: false
})
Patient.findAll({
where: {
createdAt: {
[Op.between]: [new Date('2020-02-02'), new Date()]
}
}
})
But, when I try to compile it with tsc, it reports error like:
sequelize.ts:50:5 - error TS2322: Type '{ [between]: Date[]; }' is not assignable to type 'string | number | boolean | WhereAttributeHash | AndOperator | OrOperator | Literal | Where | Fn | Col | WhereOperators | Buffer | WhereGeometryOptions | (string | ... 2 more ... | Buffer)[]'.
Types of property '[Op.between]' are incompatible.
Type 'Date[]' is not assignable to type 'string | number | boolean | [number, number] | WhereAttributeHash | AndOperator | OrOperator | Literal | Where | ... 5 more ... | (string | ... 2 more ... | Buffer)[]'.
Type 'Date[]' is not assignable to type '(string | number | WhereAttributeHash | Buffer)[]'.
Type 'Date' is not assignable to type 'string | number | WhereAttributeHash | Buffer'.
Type 'Date' is not assignable to type 'WhereAttributeHash'.
Index signature is missing in type 'Date'.
50 createdAt: {
~~~~~~~~~
Found 1 error.
It seems I cannot use Op.between with a date range? But it's ok when I wrote similar code in JS.
So I wonder if there is really something wrong in my TS code or just a missing in the type definition, or maybe using Op.between with dates is not recommended?
You're passing a date object instead of a string. Do this:
Patient.findAll({
where: {
createdAt: {
[Op.between]: [new Date('2020-02-02').toISOString(), new Date().toISOString()]
}
}
})
in my case i had:
createdAt: {
[Op.between]: [
new Date(startDate).toISOString(),
new Date(`${endDate} 23:59:59`).toISOString()
]
}
and worked changing to:
createdAt: {
[Op.and]: [
{
[Op.lt]: new Date(`${endDate} 23:59:59`),
[Op.gt]: new Date(startDate)
}
]
}

Sequelize - Association Many to Many - 3 Foreign Keys

I'm looking for some help on properly defining an association table with 3 foreign keys in a Sequelize model:
Situation: I'm building an email client, the relative models to this question are:
User Model (user records)
Thread Model (thread records for each new email-thread)
Folder Model (folders for default folders e.g. inbox, sent, etc and custom folders)
ThreadFolder Model (association linking a specific a) User Model, to a specific b) Thread Model, to a specific c) Folder Model)
Problem: My problem is the association model/table (ThreadFolder), I'm not able to create an association for all 3 tables in the ThreadFolder association Model.
First Attempt I'm able to create an association with Sequelize that allows the ThreadFolder model to create foreign keys for 2 of the three models above, but not all 3. Here is the association for that:
Thread.belongsToMany(Folder, { through: ThreadFolder, foreignKey: 'thread_id', otherKey: 'folder_id' })
Folder.belongsToMany(Thread, { through: ThreadFolder, foreignKey: 'folder_id', otherKey: 'thread_id' })
SQL Input Attempts:
user_id: 1 | thread_id: 1 | folder_id: 1 | Successful Insert
user_id: 1 | thread_id: 1 | folder_id: 2 | Successful Insert
user_id: 1 | thread_id: 2 | folder_id: 1 | Failed Insert -- Error below...
user_id: 2 | thread_id: 1 | folder_id: 1 | Successful Insert
user_id: 2 | thread_id: 1 | folder_id: 2 | Successful Insert
First Attempt's Error:
Executing:
INSERT INTO `iwantmail-core`.`thread_folders` (`user_id`, `deleted`, `archived`, `created_at`, `updated_at`, `thread_id`, `folder_id`) VALUES ('1', '0', '0', '2020-03-05 23:34:16', '2020-03-05 23:34:16', '30', '1');
Operation failed: There was an error while applying the SQL script to the database.
ERROR 1062: 1062: Duplicate entry '30-1' for key 'PRIMARY'
SQL Statement:
INSERT INTO `iwantmail-core`.`thread_folders` (`user_id`, `deleted`, `archived`, `created_at`, `updated_at`, `thread_id`, `folder_id`) VALUES ('1', '0', '0', '2020-03-05 23:34:16', '2020-03-05 23:34:16', '30', '1')
Second Attempt I can specify the association as shown below, to allow me to add records with different user_id and folder_id, however, if I use a different thread_id, I get an error shown below.
Folder.hasMany(ThreadFolder, { foreignKey: 'folder_id' })
ThreadFolder.belongsTo(Folder, { foreignKey: 'folder_id' })
Thread.belongsToMany(Folder, { through: ThreadFolder })
Thread.hasMany(ThreadFolder, { foreignKey: 'thread_id' })
ThreadFolder.belongsTo(Thread, { foreignKey: 'thread_id' })
User.hasMany(ThreadFolder, { foreignKey: 'user_id' })
ThreadFolder.belongsTo(User, { foreignKey: 'user_id' })
Folder.belongsToMany(User, { through: ThreadFolder })
SQL Input Attempts:
user_id: 1 | thread_id: 1 | folder_id: 1 | Successful Insert
user_id: 1 | thread_id: 1 | folder_id: 2 | Successful Insert
user_id: 1 | thread_id: 2 | folder_id: 1 | Failed Insert -- Error below...
user_id: 2 | thread_id: 1 | folder_id: 1 | Successful Insert
user_id: 2 | thread_id: 1 | folder_id: 2 | Successful Insert
Second Attempt's Error
Operation failed: There was an error while applying the SQL script to the database.
ERROR 1062: 1062: Duplicate entry '1-1' for key 'PRIMARY'
SQL Statement:
INSERT INTO `mail-core`.`thread_folders` (`user_id`, `thread_id`, `folder_id`) VALUES ('1', '2', '1')
Note that I'm basically trying to indicate that User#1 of Thread#1 are in Folder#1 and as soon as I try to indicate that User#1 of Thread#2 are in Folder#1, the above error occurs.
Help:
Could someone please point me towards the right direction / show how the association should be written to take into account the 3rd association?
Is there a different way to write this association all together so all 3 foreign keys are taken into account in the association table?
Thanks for any help/assistance in advance!
(Relative technologies used: MySQL, MySQL Workbench, Node 12.x, TypeScript, Serverless Framework)
EDIT: Made edits to the post, 2nd attempt was presented as a partial solution, after further testing, both 1st and second attempts fail when a 2nd user is added to the same thread and folder in the association table ThreadFolder.
After looking at your create statements I think you have defined associations thread_folders properly. In my opinion, your second attempt at defining association is correct.
You are getting an error while inserting records because your primary key is the combined key of two attributes namely thread_id and folder_id. Let us say that in your thread_folders table there is already record for thread_id 1 and folder_id 1 then you can not insert another record with thread_id 1 and folder_id 1.
If you remove the combined primary key of thread_id and folder_id then you will be able to insert the records that you want to insert in the thread_folders table.
I hope it helps!
Working solution ended up being a variance of the second attempt:
User.hasMany(ThreadFolder, { foreignKey: 'user_id' })
ThreadFolder.belongsTo(User, { foreignKey: 'user_id' })
Folder.hasMany(ThreadFolder, { foreignKey: 'folder_id' })
ThreadFolder.belongsTo(Folder, { foreignKey: 'folder_id' })
Thread.hasMany(ThreadFolder, { foreignKey: 'thread_id' })
ThreadFolder.belongsTo(Thread, { foreignKey: 'thread_id' })
Most of my model calls (based on how they were written before) will end up changing to start with the association table first e.g.
ThreadFolder.findAll({
where: {
user_id: 1,
folder_id: 1,
},
include: [
{
model: Thread,
include: [
'recipient',
'sender'
]
}
]
})
Hope this helps others that have attempted to do a multi-foreign key association table beyond 2 foreign keys, presumable this approach should work with any amount of foreign keys in the association table.

onDelete cascade not working on "one to one" - Sequelize.js

My category table has a foreign key: name_id which is referenced to id of translation table, now category and translation have one to one relationship.
I would like to add a onDelete CASCADE so when I delete category the translation would also be deleted.
const Translation = sequelize.define('Translation',
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
en: DataTypes.STRING(1000),
es: DataTypes.STRING(1000),
pt: DataTypes.STRING(1000)
}
)
const Category = sequelize.define('Category',
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
nameId: DataTypes.INTEGER
}
)
Category.belongsTo(Translation, { as: 'name', foreignKey: 'nameId', onDelete: 'cascade' })
Translation.hasOne(Category, { foreignKey: 'nameId', onDelete: 'cascade' })
Is there something I am missing?
P.S. other tables will also be related to translations, that´s why foreign key is placed in category and not the other way around.
Your association is on the wrong side! With such association definition, you will get the following scheme:
+----------+
| Category |
+----------+
| id |
+----------+
| nameId |
+----------+
+-------------+
| Translation |
+-------------+
| id |
+-------------+
| en |
+-------------+
| es |
+-------------+
| pt |
+-------------+
| nameId | <-- references Category.id
+-------------+
Therefore, your cascade is working, but it will delete orphaned Categories when a Translation is dropped. To achieve what you seek, you must define your association like so:
Category.hasOne(Translation, { as: 'name', foreignKey: 'nameId', onDelete: 'cascade' })
Translation.belongsTo(Category, { foreignKey: 'nameId', onDelete: 'cascade' })
Edit: If you have multiple tables with translations, you need to drop the belongTo associations. You will only have the hasOne on each of these tables, ex:
Category.hasOne(Translation, { as: 'name', foreignKey: 'nameId', onDelete: 'cascade' });
Event.hasOne(Translation, { as: 'description', foreignKey: 'descriptionId', onDelete: 'cascade' });