TypeORM update doesn't work for relationship but save does - nestjs

I'm trying to update one record and its relation associated but having a hard time doing so.
I tried with the save method this way and it works:
await this.orderRepository.save(
{
...orderRecord,
orderStatus: mappedOrderStatus,
shipments: [...orderRecord.shipments, shipment], // [..., ..., ...]
},
);
orderRecord:
Order {
id: 1,
createdAt: 2023-01-23T15:56:25.733Z,
updatedAt: 2023-01-23T15:56:25.733Z,
orderNumber: 174,
shipments: [
Shipment {
id: 1,
createdAt: 2023-01-23T15:56:25.433Z,
updatedAt: 2023-01-23T15:56:25.433Z,
orders: [Array]
}
]
}
However If I try to do the same thing but using update method, it gives me the following error: Cannot query across many-to-many for property shipments
await this.orderRepository.update(orderRecord.id, {
orderStatus: mappedOrderStatus,
shipments: [...orderRecord.shipments, asd],
});
order.entity.ts
#ManyToMany(() => Shipment,
(shipment) => shipment.orders)
shipments: Shipment[]
shipment.entity.ts
#ManyToMany(() => Order,
(order) => order.shipments,
{ cascade: true }
)
#JoinTable()
orders: Order[]
What am I missing?

Go ahead with save method, it seems like inserting relational data in many-to-many case is not implemented for update yet.
This might not be related to the driver. the issue is still open in github.

Related

How can I optimize my MongoDB Upsert statement?

A decision was made to switch our database from SQL to noSQL and I have a few questions on best practices and if my current implementation could be improved.
My current SQL implementation for upserting player data after a game.
let template = Players.map(
(player) =>
`(
${player.Rank},"${player.Player_ID}","${player.Player}",${player.Score},${tpp},1
)`,
).join(',');
let stmt = `INSERT INTO playerStats (Rank, Player_ID, Player, Score, TPP, Games_Played)
VALUES ${template}
ON CONFLICT(Player_ID) DO UPDATE
SET Score = Score+excluded.Score,
Games_Played=Games_Played+1,
TPP=TPP+excluded.TPP`;
db.run(stmt, function (upsert_error) { ...
The expected code is to update existing players by checking if a current Player_id exist. If so update their score among other things. Else insert a new player.
Mongo Implementation
const players = [
{ name: 'George', score: 10, id: 'g65873' },
{ name: 'Wayne', score: 100, id: 'g63853' },
{ name: 'Jhonny', score: 500, id: 'b1234' },
{ name: 'David', score: 3, id: 'a5678' },
{ name: 'Dallas', score: 333333, id: 'a98234' },
];
const db = client.db(dbName);
const results = players.map((player) => {
// updateOne(query, update, options)
db.collection('Players')
.updateOne(
{ Player_Name: player.name },
{
$setOnInsert: { Player_Name: player.name, id: player.id },
$inc: { Score: player.score },
},
{ upsert: true, multi: true },
);
});
Is there a better way in mongo to implement this? I tried using updateMany and bulkUpdate and I didn't get the results I expected.
Are there any tips, tricks, or resources aside from the mongo.db that you would recommend for those moving from SQL to noSQL?
Thanks again!
Your approach is fine. However, there are a few flaws:
Command updateOne updates exactly one document as the name implies. Thus multi: true
is obsolete.
Field names are case-sensitive (unlike most SQL databases). It should be $inc: { score: player.score }, not "Score"
Field Player_Name does not exist, it will never find any document for update.
So, your command should be like this:
db.collection('Players').updateOne(
{ name: player.name }, //or { id: player.id } ?
{
$setOnInsert: { name: player.name, id: player.id },
$inc: { score: player.score },
},
{ upsert: true }
)
According to my experience, moving from SQL to NoSQL is harder if you try to translate the SQL statement you have in your mind into a NoSQL command one-by-one. For me it worked better when I wiped out the SQL idea and try to understand and develop the NoSQL command from scratch.
Of course, when you do your first find, delete, insert, update then you will see many analogies to SQL but latest when you approach to the aggregation framework you are lost if you try to translate them into SQL or vice versa.

Mongoose unique if not null and if state

I have a unique index like this
code: {
type: String,
index: {
unique: true,
partialFilterExpression: {
code: { $type: 'string' }
}
},
default: null
},
state: { type: Number, default: 0 },
but When the state is 2 (archived) I want to keep the code, but it should be able to reuse the code, so it cannot be unique if state is 2.
Is there any away that I could accomplish this?
This is possible, though it's through a work around documented here https://jira.mongodb.org/browse/SERVER-25023.
In MongoDB 4.7 you will be able to apply different index options to the same field but for now you can add a non-existent field to separate the two indexes.
Here's an example using the work around.
(async () => {
const ItemSchema = mongoose.Schema({
code: {
type: String,
default: null
},
state: {
type: Number,
default: 0,
},
});
// Define a unique index for active items
ItemSchema.index({code: 1}, {
name: 'code_1_unique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 0}}
]
},
unique: true
})
// Defined a non-unique index for non-active items
ItemSchema.index({code: 1, nonExistantField: 1}, {
name: 'code_1_nonunique',
partialFilterExpression: {
$and: [
{code: {$type: 'string'}},
{state: {$eq: 2}}
]
},
})
const Item = mongoose.model('Item', ItemSchema)
await mongoose.connect('mongodb://localhost:27017/so-unique-compound-indexes')
// Drop the collection for test to run correctly
await Item.deleteMany({})
// Successfully create an item
console.log('\nCreating a unique item')
const itemA = await Item.create({code: 'abc'});
// Throws error when trying to create with the same code
await Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code')})
// Change the active code
console.log('\nChanging item state to 2')
itemA.state = 2;
await itemA.save();
// Successfully created a new doc with sama code
await Item.create({code: 'abc'})
.then(() => console.log('\nSuccessfully created a new doc with sama code'))
.catch(() => console.log('\nThrowing a duplicate error'));
// Throws error when trying to create with the same code
Item.create({code: 'abc'})
.catch(err => {console.log('\nThrowing a duplicate error when creating with the same code again')})
})();
This is not possible with using indexes. Even if you use a compound index for code and state there will still be a case where
new document
{
code: 'abc',
state: 0
}
archived document
{
code: 'abc',
state: 2
}
Now although you have the same code you will not be able to archive the new document or unarchive the archived document.
You can do something like this
const checkCode = await this.Model.findOne({code:'abc', active:0})
if(checkCode){
throw new Error('Code has to be unique')
}
else{
.....do something
}

NestJS TypeORM Post Request for a Has Many Through Type Relationship

I'm attempting to implement a many to many relationship with a custom field exactly like this illustrates. My data is different than the example he gives, however the structure of the relationship is identical. I am able to successfully create the relationships, but I'm having an issue where the foreign key for product (product_id) is null even though the client is sending data for that field. For some reason TypeORM is dropping it and inputting a null value.
Here is the structure of the junction table:
#Entity()
export class ShipmentProduct {
#PrimaryGeneratedColumn()
id: number;
#Column()
qty: number;
#Column()
pick_up: boolean;
#ManyToOne(type => Shipment, shipment => shipment.shipmentProducts)
#JoinColumn({ name: 'shipment_id' })
shipment: Shipment;
#ManyToOne(type => Product, product => product.shipmentProducts)
#JoinColumn({ name: 'product_id' })
product: Product;
}
My Service submits the data by the following function:
async create(shipmentData: CreateShipmentDto): Promise<Shipment> {
const shipment = new Shipment();
shipment.bill_of_lading = shipmentData.bill_of_lading;
shipment.trailer_number = shipmentData.trailer_number;
shipment.ship_date = shipmentData.ship_date;
shipment.delivery_date = shipmentData.delivery_date;
shipment.carrier = shipmentData.carrier;
shipment.release_code = shipmentData.release_code;
shipment.payment_date = shipmentData.payment_date;
shipment.comments = shipmentData.comments;
shipment.client = await this.clientsRepository.findOne(shipmentData.client_id);
shipment.shipmentProducts = shipmentData.shipmentProducts;
return await this.shipmentsRepository.save(shipment);
}
when submitting the form, shipment data is saved successfully along with shipmentProducts, however the product_id is dropped even though shipmentData contains a value for product_id.
This is a console.log of shipmentData
{
client_id: 1,
bill_of_lading: '12',
trailer_number: '21',
ship_date: '2020-04-02T04:00:00.000Z',
delivery_date: '',
carrier: '21',
release_code: '21',
fuel_surcharge: '21',
payment_date: '',
comments: '',
shipmentProducts: [
{ product_id: 1966, qty: '12', pick_up: false },
{ product_id: 1966, qty: '12', pick_up: false }
]
}
However, the insert statement for shipmentProducts omits product_id and insists on a default value. Why? How do I fix it?
INSERT INTO `shipment_product`(`id`, `qty`, `pick_up`, `shipment_id`, `product_id`) VALUES (DEFAULT, ?, ?, ?, DEFAULT) -- PARAMETERS: ["12",0,10]
Turns out I needed to send at least a partial product object in my post request and not just product_id. So this seemed to do the trick. Problem was with the client side and not NestJS or TypeORM.
shipmentProducts: [
{ product: { id:1966 }, qty: 12, pick_up: false }
{ product: { id:1845 }, qty: 20, pick_up: false }
]
This is quite common problem.
Useful workaround is to use:
#Entity()
export class ShipmentProduct {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(type => Product, product => product.shipmentProducts)
#JoinColumn({ name: 'product_id' })
product: Product;
#Column({name: 'product_id'})
productId: Product['id'];
}
So you can pass productId directly.

Sequelize ORM return a weird response after inner join tables in nodejs

I use sequelize orm to manage my data base (mysql).
I make a inner join that work good but the problem that the table that join return a weird variable.
this is my code
const getReports = id => {
return new Promise((resolve, reject) => {
models.Report.findAll({
where: { companyID: [513603324, 515490704, 511493827] },
include: [{
model: models.Reports_type,
attributes:["name"],
required: true
}],
raw: true
})
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
};
The output is
[
{
"id": 8,
"creatorUserID": 1,
"currentUserEditorID": 1,
"companyID": 511493827,
"stageID": 1,
"scenarioID": 1,
"typeID": 1,
"year": 2020,
"deadLine": "2019-10-30T22:00:00.000Z",
"createdAt": "2019-10-29T08:31:19.000Z",
"updatedAt": "2019-10-29T08:31:19.000Z",
"Reports_type.name": "excelent",
"companyName": "energy",
}
]
The problem is i get it weird like this:
"Reports_type.name"
I want the output be:
"name"
This topic has been covered before - see this.
To avoid the prefix, attributes must be specified in the main model rather than the included model. The example below should produce all fields in Report plus Reports_type.name. Note: the alias of Reports_type may be a little different than I've guessed - if you get a "field does not exist", find the correct alias from the generated SQL.
models.Report.findAll({
where: { companyID: [513603324, 515490704, 511493827] },
include: [{
model: models.Reports_type,
attributes:[], // suppress here
required: true
}],
raw: true,
attributes: {
include: [[Sequelize.col("reports_types.name"), "name"]] // include here; table alias may be a little different!
}
})

Sequelize query on join table

I am trying to querying a join table using sequelize:
Here is the model:
db.client.belongsToMany(db.user, {
through: db.clientUser,
onDelete: 'cascade',
});
db.user.belongsToMany(db.client, {
through: db.clientUser,
});
and this is what I am trying to do:
db.user.findAll({
where: {
group_id: 1,
},
include: [{
model: db.clientUser,
where: {
is_manager: 1,
}
}],
raw: true,
})
However I get the following error: client_user is not associated to user!
Any idea what could be the cause of this issue?
You declared a relationship between client from user through clientUser. Although pedantic, its complaint is technically correct: there is no explicitly declared relationship declared between client and clientUser. Nor should there be: your belongsToMany relationship should take care of that. Your query can be adjusted to work with this relationship.
Note: I don't know what tables group_id and is_manager are found in. They may need to be shuffled around.
db.user.findAll({
where: {
group_id: 1,
},
include: [{
model: db.client,
through: {
where: {
is_manager: 1, // Assuming clientUser.is_manager?
},
}],
raw: true,
})

Resources