Unable to bulk upsert using sequelize - express

I am using sequelize(5.21.6) with sqlite3(^4.1.1).
I have two models A and B with many to many relation between them.
I want to bulk insert or update into the pivot table.
To achieve this I created a third model for the pivot table, lets call it AB.
So here is how I am trying to bulk upsert into pivot table.
db.AB.bulkCreate([array of values], {
updateOnDuplicate: ['aId', 'bId', 'updatedAt']
});
The error which I get:
{ Error: SQLITE_ERROR: near ")": syntax error
[0] errno: 1,
[0] code: 'SQLITE_ERROR',
[0] sql: INSERT INTO `AB` (`id`,`aId`,`bId`,`createdAt`,`updatedAt`) VALUES (19,1,1,'example',\'2020-04-15 15:18:59.000 +00:00\',\'2030-04-15 15:18:59.000 +00:00\'),(20,2,2,\'2030-04-15 15:19:49.000 +00:00\',\'2030-04-15 15:19:49.000 +00:00\') ON CONFLICT () DO UPDATE SET `aId`=EXCLUDED.`aId`, `bId`=EXCLUDED.`bId`,`updatedAt`=EXCLUDED.`updatedAt`;'
}
My AB model definition:
'use strict';
module.exports = (sequelize, DataTypes) => {
const AB = sequelize.define('AB', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true
},
aId: DataTypes.INTEGER,
bId: DataTypes.INTEGER
}, {});
AB.associate = function (models) {
// associations can be defined here
AB.belongsTo(models.A, { foreignKey: 'aId' });
AB.belongsTo(models.B, { foreignKey: 'bId' });
};
return AB;
};

Related

SQL query to PostgeSQL tables [duplicate]

This question already has answers here:
PostgreSQL "Column does not exist" but it actually does
(6 answers)
sql statement error: "column .. does not exist"
(1 answer)
Postgresql Column Not Found, But Shows in Describe
(1 answer)
Closed 2 years ago.
I have a problem.
SELECT *
FROM posts CROSS JOIN public."postReactions" AS reactions
WHERE posts.userId = '423abb9e-a00d-4045-9e88-4a85897f67e4'
But the response from DB is like 'ERROR: column posts.userid doesn't exist.
LINE 3: WHERE posts.userId = '423abb9e-a00d-4045-9e88-4a85897f67e4'.
Result of CROSS JOIN below:
Posts table
PostReactions table
Sequelize models below:
export default models => {
const {
User,
Post,
PostReaction,
PostNegativeReaction,
Comment,
Image
} = models;
User.hasMany(Post);
User.hasMany(PostReaction);
Post.belongsTo(User);
Post.hasMany(PostReaction);
PostReaction.belongsTo(Post);
PostReaction.belongsTo(User);
};
This is my associations:
export default {
up: (queryInterface, Sequelize) => queryInterface.sequelize
.transaction(transaction => Promise.all([
queryInterface.addColumn('posts', 'userId', {
type: Sequelize.UUID,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
}, { transaction }),
queryInterface.addColumn('postReactions', 'userId', {
type: Sequelize.UUID,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
}, { transaction }),
queryInterface.addColumn('postReactions', 'postId', {
type: Sequelize.UUID,
references: {
model: 'posts',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
}, { transaction }),
])),
down: queryInterface => queryInterface.sequelize
.transaction(transaction => Promise.all([
queryInterface.removeColumn('posts', 'userId', { transaction }),
queryInterface.removeColumn('postReactions', 'userId', { transaction }),
queryInterface.removeColumn('postReactions', 'postId', { transaction })
]))
};
MERN stack.
Main problem: how to do a request to db that will receive the response with POSTS body (only liked by the current user).
This would be possible if the table were created using escaped column names . . . "postId" rather than postId.
I suspect that is the problem. The best solution is to recreate the table and not escape any identifiers, ever. Alternatively, you can use the escaped column name.

Sequelize query with a where clause on an include of an include

I'm struggling to create a query with sequelize.
Some context
I have the following models:
A Manifestation can have [0..n] Event
An Event belongs to one Manifestation (an Event cannot exist without a Manifestation)
A Place can have [0..n] Event
An Event belongs to one Place (an Event cannot exist without a Place)
A Manifestation can have [1..n] Place
A Place can have [0..n] Manifestation
I model the relations as the following:
Manifestation.hasMany(Event, { onDelete: 'CASCADE', hooks: true })
Event.belongsTo(Manifestation)
Place.hasMany(Event, { onDelete: 'CASCADE', hooks: true })
Event.belongsTo(Place)
Manifestation.belongsToMany(Place, { through: 'manifestation_place' })
Place.belongsToMany(Manifestation, { through: 'manifestation_place' })
For me it seems rather correct, but don't hesitate if you have remarks.
The question
I'm trying to query the Place in order to get all Manifestation and Event happening in a given Place. But for the Event ones, I want to include them within their Manifestation even if the Manifestation doesn't happen in the given Place.
Below is the "JSON" structure I'm trying to achieve:
{
id: 1,
name: "Place Name",
address: "Place address",
latitude: 47.00000,
longitude: -1.540000,
manifestations: [
{
id: 10,
title: "Manifestation one",
placeId: 1,
events: []
},
{
id: 11,
title: "Manifestation two",
placeId: 3,
events: [
id: 5,
title: "3333",
manifestationId: 11,
placeId: 1
]
}
]
}
So I want to include the Manifestation with id: 11, because one of its Event occurs in the given Place (with id: 1)
Update (04/06/20): For now I rely on javascript to get the expected result
I figured out it would be nice if I posted my current solution before asking.
router.get('/test', async (req, res) => {
try {
const placesPromise = place.findAll()
const manifestationsPromise = manifestation.findAll({
include: [
{ model: event },
{
model: place,
attributes: ['id'],
},
],
})
const [places, untransformedManifestations] = await Promise.all([
placesPromise,
manifestationsPromise,
])
const manifestations = untransformedManifestations.map(m => {
const values = m.toJSON()
const places = values.places.map(p => p.id)
return { ...values, places }
})
const result = places
.map(p => {
const values = p.toJSON()
const relatedManifestations = manifestations
.filter(m => {
const eventsPlaceId = m.events.map(e => e.placeId)
return (
m.places.includes(values.id) ||
eventsPlaceId.includes(values.id)
)
})
.map(m => {
const filteredEvents = m.events.filter(
e => e.placeId === values.id
)
return { ...m, events: filteredEvents }
})
return { ...values, manifestations: relatedManifestations }
})
.filter(p => p.manifestations.length)
return res.status(200).json(result)
} catch (err) {
console.log(err)
return res.status(500).send()
}
})
But I'm pretty sure I could do that directly with sequelize. Any ideas or recommendations ?
Thanks
This is not optimum. But you can try it out:
const findPlace = (id) => {
return new Promise(resolve => {
db.Place.findOne({
where: {
id: id
}
}).then(place => {
db.Manefestation.findAll({
include: [{
model: db.Event,
where: {
placeId: id
}
}]
}).then(manifestations => {
const out = Object.assign({}, {
id: place.id,
name: place.name,
address: place.address,
latitude: place.latitude,
longitude: place.longitude,
manifestations: manifestations.reduce((res, manifestation) => {
if (manifestation.placeId === place.id || manifestation.Event.length > 0) {
res.push({
id: manifestation.id,
title: manifestation.id,
placeId: manifestation.placeId,
events: manifestation.Event
})
}
return res;
}, [])
})
})
resolve(out);
})
})
}
From this, you get all manifestations that assigned to place or have any event that assigns. All included events in the manefestations are assigned to the place.
Edit :
You will be able to use the following one too:
const findPlace = (id) => {
return new Promise(resolve => {
db.Place.findOne({
include: [{
model: db.Manefestation,
include: [{
model: db.Event,
where: {
placeId: id
}
}]
}],
where: {
id: id
}
}).then(place => {
db.Manefestation.findAll({
include: [{
model: db.Event,
where: {
placeId: id
}
}],
where: {
placeId: {
$not: id
}
}
}).then(manifestations => {
place.Manefestation = place.Manefestation.concat(manifestations.filter(m=>m.Event.length>0))
resolve(place);// or you can rename, reassign keys here
})
})
})
}
Here I take only direct manifestations in the first query. Then, manifestations that not included and concatenate.
I do not know if you figure it out by now. But the solution is provided below.
Search with Sequelize could get funny :). You have to include inside another include. If the query gets slow use separate:true.
Place.findAll({
include: [
{
model: Manifestation,
attributes: ['id'],
include: [{
model: Event ,
attributes: ['id']
}]
},
],
})
I tried to complete it in a single query but you will still need JavaScript to be able to get the type of output that you want.
(Note: 💡 You need manifestation which is not connected to places but should be included if a event is present of that place. The only SQL way to get that starts by doing a CROSS JOIN between all tables and then filtering out the results which will be a very hefty query)
I came up with this code(tried & executed) which doesn't need you to execute 2 findAll that fetches all data as what you are currently using. Instead it fetched only the data needed for final output in 1 query.
const places = await Place.findAll({
include: [{
model: Manifestation,
// attributes: ['id']
through: {
attributes: [], // this helps not get keys/data of join table
},
}, {
model: Event,
include: [{
model: Manifestation,
// attributes: ['id']
}],
}
],
});
console.log('original output places:', JSON.stringify(places, null, 2));
const result = places.map(p => {
// destructuring to separate out place, manifestation, event object keys
const {
manifestations,
events,
...placeData
} = p.toJSON();
// building modified manifestation with events array
const _manifestations = manifestations.map(m => {
return ({ ...m, events: [] })
});
// going through places->events to push them to respective manifestation events array
// + add manifestation which is not directly associated to place but event is of that manifestation
events.map(e => {
const {
manifestation: e_manifestation, // renaming variable
...eventData
} = e;
const mIndex = _manifestations.findIndex(m1 => m1.id === e.manifestationId)
if (mIndex === -1) { // if manifestation not found add it with the events array
_manifestations.push({ ...e_manifestation, events: [eventData] });
} else { // if found push it into events array
_manifestations[mIndex].events.push(eventData);
}
});
// returning a place object with manifestations array that contains events array
return ({ ...placeData, manifestations: _manifestations });
})
// filter `.filter(p => p.manifestations.length)` as used in your question
console.log('modified places', JSON.stringify(result, null, 2));

Join 3 Tables with Sequelize

I'm trying to convert below raw query to ORM but couldn't achieve anything.
raw query
select * from "Invoices" I
LEFT JOIN "Requests" R ON R."id" = I."requestId"
LEFT JOIN "Supps" S ON S."requestId" = R."id"
where S."confirm" = 'OK'
What I've tried:
Requests.belongsTo(Invoices, { foreignKey: "id" });
Supps.belongsTo(Requests, { foreignKey: "requestId" });
Supps.hasMany(Requests, { foreignKey: "requestId" });
Invoices.hasMany(Requests, { foreignKey: "requestId" });
Invoices.findOne({
include: [{ model: Requests, required: false }, { model: Supps, required: false }],
where: { Sequelize.col("Supps.confirm"): { "OK" } }
}).then(data => {
console.log(data);
})
But this generates a very very long query with multiple sub queries and wrong data
For sure you will need to add also the sourceKey in th associations. (doc)
Supps.hasMany(Requests, { foreignKey: "requestId" ,sourceKey: '{ID}' });
Invoices.hasMany(Requests, { foreignKey: "requestId",sourceKey: '{ID}' });
Also you can add {raw:true} to get a one level json response .
Invoices.findOne({
include: [{ model: Requests, required: false }, { model: Supps, required: false }],
where: { Sequelize.col("Supps.confirm"): { "OK" } },
raw:true
}).then(data => {
console.log(data);
});

AWS RDS BatchExecuteStatementRequest

I am trying to update my RDS Aurora database with simple information.
import * as AWS from 'aws-sdk';
import { BatchExecuteStatementRequest } from 'aws-sdk/clients/rdsdataservice';
const RDS = new AWS.RDSDataService();
export const create = async (event: any) => {
try {
const params: BatchExecuteStatementRequest = {
secretArn: 'arn:aws:secretsmanager:XXXXXXXXXXXXXXXXXXX',
resourceArn: 'arn:aws:rds:XXXXXXXXXXXXXXXXXXXXX',
schema: 'PUBLIC',
database: 'test_db',
parameterSets: [
[
{
name: 'FirstName',
value: {
stringValue: 'Joe',
}
},
{
name: 'LastName',
value: {
stringValue: 'Doe'
}
}
],
[
{
name: 'FirstName',
value: {
stringValue: 'Joyer',
}
},
{
name: 'LastName',
value: {
stringValue: 'Doyer'
}
}
]
],
sql: 'INSERT INTO test_table (FirstName, LastName) VALUES (:FirstName, :LastName)'
};
const res = await RDS.batchExecuteStatement(params).promise();
console.log({ result: res, params });
return {
statusCode: 200,
body: JSON.stringify(res)
};
} catch (err) {
console.error(err);
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Could not create the note.'
};
}
};
This will generate an error:
{ BadRequestException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ’(’Joyer’, ‘Doyer’)' at line 1 }
The weird part is, it's working if I only add ONE person in parameterSets, so the error occur when I try to have more than one array.
I created my db in AWS console, this is the query for that:
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE IF NOT EXISTS test_table (
Id int NOT NULL AUTO_INCREMENT,
FirstName varchar(255),
LastName varchar(255),
PRIMARY KEY (Id)
);
I found the problem while I was writing the question, thought I will share it here anyway.
You can NOT have AUTO_INCREMENT!
This was the error, just remove it and everything works.
This was a couple of fun hours.....

Sequelize Many to Many Relationship using Through does not insert additional attributes

I have a many to many relationship between: Step and Control Through ControlsConfig.
When creating a Control object and call addStep function and specify the additional attributes (which exist in the relation table), Sequelize creates the records in the relational table ControlsConfig but the additional attributes are NULLs.
PS: The tables are creating correctly in the database.
Table 1: Step
Table 2: Control
Relation table: ControlsConfig
Step
var Step = sequelize.define('Step', {
title: { type: DataTypes.STRING, allowNull: false },
description: DataTypes.STRING,
type: { type: DataTypes.ENUM('task', 'approval'), allowNull: false, defaultValue: 'task' },
order: DataTypes.INTEGER
});
Step.associate = function(models) {
models.Step.belongsTo(models.User);
models.Step.belongsTo(models.Template);
models.Step.hasMany(models.Action);
};
Control
var Control = sequelize.define('Control', {
label: { type: DataTypes.STRING, allowNull: false },
order: { type: DataTypes.INTEGER },
type: { type: DataTypes.ENUM('text', 'yes/no') },
config: { type: DataTypes.TEXT },
controlUiId: { type: DataTypes.STRING }
});
Control.associate = function(models) {
models.Control.belongsTo(models.Section);
};
ControlsConfigs
module.exports = (sequelize, DataTypes) => {
var ControlsConfig = sequelize.define('ControlsConfig', {
visibility: { type: DataTypes.ENUM('hidden', 'readonly', 'editable', 'required') },
config: { type: DataTypes.TEXT }
});
ControlsConfig.associate = function(models) {
models.Control.belongsToMany(models.Step, { through: models.ControlsConfig });
models.Step.belongsToMany(models.Control, { through: models.ControlsConfig });
models.ControlsConfig.belongsTo(models.Template);
};
return ControlsConfig;
};
Insertion:
try {
var step1 = await Step.create({ /*bla bla*/ });
var control1 = await Control.create({ /*bla bla*/ });
var OK = await control1.addStep(step1, {through: { config: 'THIS FIELD ALWAYS APPEARS NULL' }});
} catch (error) { /* No errors*/ }
I am following the same strategy stated at the documentation
//If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
const User = sequelize.define('user', {})
const Project = sequelize.define('project', {})
const UserProjects = sequelize.define('userProjects', {
status: DataTypes.STRING
})
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })
//To add a new project to a user and set its status, you pass extra options.through to the setter, which contains the attributes for the join table
user.addProject(project, { through: { status: 'started' }})
You have to pass edit: true to the addProject and addStep method.
See this answer it has a similar issue
Sequelize belongsToMany additional attributes in join table