How can I upsert many fields in prisma ORM with one query?
I don't want to use upsert fields one by one. Can I upsert all of them with one query?
You can't do it right now in Prisma.
Most efficient way if you need to handle lots of data would probably be something like that:
prisma.$transaction([
prisma.posts.deleteMany({ where: { userId: 1 } }),
prisma.posts.createMany({
{ id: 1, title: 'first', userId: 1 },
{ id: 2, title: 'second', userId: 1 },
{ id: 3, title: 'third', userId: 1 },
}),
]);
So you delete existing records and then recreate them again inside of a transaction.
Depending on the database (and schema) you use, Prisma supports an optional boolean within a createMany call: skipDuplicates, see https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#createmany
Do not insert records with unique fields or ID fields that already exist. Only supported by databases that support ON CONFLICT DO NOTHING.
I got the following data model: https://imgur.com/a/AwwpW9F
Basically, A User can belong to many Projects, and a Project can have many Users and I'm tying that together through a join table called UserProjects
With a raw SQL query I can go
SELECT "user".email, project.name FROM "user"
JOIN userprojects ON userprojects.user_id = "user".id
JOIN project ON project.id = userprojects.project_id
Which gives me
email(On User table) name(On Project table)
first#email.com Project X
first#email.com Project Y
second#email Project Y
How would I structure this query with Objection ORM? Perhaps I can just do a raw query straight? Something like
User.query().raw(SELECT "user".email, project.name FROM "user"
JOIN userprojects ON userprojects.user_id = "user".id
JOIN project ON project.id = userprojects.project_id)
?
instead of doing all the stuff by yourself, Objection.js can do it for you. You can just declare a ManyToManyRelation.
static relationMappings = {
projects: {
relation: Model.ManyToManyRelation,
modelClass: Project, // imported Objection class of "Project"
join: {
from: 'user.id',
through: {
from: 'userprojects.user_id',
to: 'userprojects.project_id'
},
to: 'project.id'
}
}
}
Then you can just get the projects of a User using eager loading:
User.query().eager('projects').findById(userId)
And you will get something like:
User {
id: 3,
firstname: 'firstname',
lastname: 'lastname',
email: 'email',
projects: [
{id: 1,
name: 'name1'},
{id: 2,
name: 'name2'},
]
}
2020 Update:
Since version 2 of Objection.js, eager method has been renamed as withGraphFetched:
User.query().withGraphFetched('projects').findById(userId)
Nvm, found a solution
/close
JK,
Here's what worked for me if anyone else would run into the same issue:
return User.query().where("user_id", parent.id)
.join('userprojects', 'user.id', '=', 'userprojects.user_id')
.join('project', 'project.id', '=', 'userprojects.project_id')
.select('user.id', 'userprojects.project_id', 'project.name')
I'm using node v9.5, sequelize v4.33 (postgres dialect).
I have two first-class models: Driver (specific people) and Car (generic make+model combinations). Thus far, they've been connected by a many-to-many join table. Now I want to start tracking additional properties on that join table, but am having trouble declaring these relationships so they actually work.
const Driver = sqlz.define('Driver', {
id: { primaryKey: true, type: DataTypes.UUID },
name: DataTypes.string
})
const Car = sqlz.define('Car', {
id: { primaryKey: true, type: DataTypes.UUID },
make: DataTypes.string,
model: DataTypes.string
})
// old associations; worked great when requirements were simpler
Driver.belongsToMany(Car, {
through: 'DriverCar',
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: 'DriverCar',
as: 'driverList',
foreignKey: 'carId'
})
Now I want to begin tracking more information about the relationship between a car and its driver, like the color of that specific car.
Step 1: I update the migration script, adding a new column to the join table like so:
queryInterface.createTable( 'DriverCar', {
driverId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Driver',
key: 'id'
}
},
carId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Car',
key: 'id'
}
},
createdAt: {
type: sqlz.DATE,
allowNull: false
},
updatedAt: {
type: sqlz.DATE,
allowNull: false
},
// new column for join table
color: {
type: Sequelize.STRING
}
})
Step 2: I define a new sqlz model for DriverCar:
const DriverCar = sqlz.define('DriverCar', {
color: DataTypes.string
})
(I assume I only need to define the interesting properties, and that driverId and carId will still be inferred from the associations that will be defined.)
Step 3: I need to update the associations that exist among Driver, Car, and DriverCar.
This is where I'm stuck. I have attempted updating the existing associations, like so:
Driver.belongsToMany(Car, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'driverList',
foreignKey: 'carId'
})
This executes without error, but the new color property is not fetched from the join table when I try driver.getCarList(). (Sqlz is configured to log every SQL statement, and I have verified that no properties from the join table are being requested.)
So, instead, I tried spelling out this relationship more explicitly, by associating Driver to DriverCar, and then Car to DriverCar:
// Driver -> Car
Driver.hasMany(DriverCar, {
as: 'carList',
foreignKey: 'driverId'
})
// Car -> Driver
Car.hasMany(DriverCar, {
foreignKey: 'carId'
})
I also tell sqlz that DriverCar won't have a standard row id:
DriverCar.removeAttribute('id')
At this point, requesting a Driver's carList (driver.getCarList()) seems to work, because I can see join table props being fetched in SQL. But saving fails:
driverModel.setCarList([ carModel1 ])
UPDATE DriverCar
SET "driverId"='a-uuid',"updatedAt"='2018-02-23 22:01:02.126 +00:00'
WHERE "undefined" in (NULL)
The error:
SequelizeDatabaseError: column "undefined" does not exist
I assume this error is occurring because sqzl doesn't understand the proper way to identify rows in the join table, because I've failed to establish the necessary associations. And frankly, I'm not confident I've done this correctly; I'm new to ORMs, but I was expecting I'd need to specify 4 assocations:
Driver -> DriverCar
DriverCar -> Car
Car -> DriverCar
DriverCar -> Driver
To recap: I have 2 first-class entities, joined in a many-to-many relationship. I'm trying to add data to the relationship, have discovered that the ORM requires defining those associations differently, and am having trouble articulating the new associations.
A note about your aliases
Before going to the answer, I would like to point out that your choice of aliases (carList and driverList) could be better, because although the auto-generated sequelize methods .setCarList() and .setDriverList() do make sense, the methods .addCarList(), .addDriverList(), .removeCarList() and .removeDriverList() are nonsense, since they take only a single instance as a parameter, not a list.
For my answer, I won't use any aliases, and let Sequelize default to .setCars(), .setDrivers(), .addCar(), .removeCar(), etc, which make much more sense to me.
Example of working code
I've made a 100% self-contained code to test this. Just copy-paste it and run it (after running npm install sequelize sqlite3):
const Sequelize = require("sequelize");
const sequelize = new Sequelize({ dialect: 'sqlite', storage: 'db.sqlite' });
const Driver = sequelize.define("Driver", {
name: Sequelize.STRING
});
const Car = sequelize.define("Car", {
make: Sequelize.STRING,
model: Sequelize.STRING
});
const DriverCar = sequelize.define("DriverCar", {
color: Sequelize.STRING
});
Driver.belongsToMany(Car, { through: DriverCar, foreignKey: "driverId" });
Car.belongsToMany(Driver, { through: DriverCar, foreignKey: "carId" });
var car, driver;
sequelize.sync({ force: true })
.then(() => {
// Create a driver
return Driver.create({ name: "name test" });
})
.then(created => {
// Store the driver created above in the 'driver' variable
driver = created;
// Create a car
return Car.create({ make: "make test", model: "model test" });
})
.then(created => {
// Store the car created above in the 'car' variable
car = created;
// Now we want to define that car is related to driver.
// Option 1:
return car.addDriver(driver, { through: { color: "black" }});
// Option 2:
// return driver.setCars([car], { through: { color: "black" }});
// Option 3:
// return DriverCar.create({
// driverId: driver.id,
// carId: car.id,
// color: "black"
// });
})
.then(() => {
// Now we get the things back from the DB.
// This works:
return Driver.findAll({ include: [Car] });
// This also works:
// return car.getDrivers();
// This also works:
// return driver.getCars();
})
.then(result => {
// Log the query result in a readable way
console.log(JSON.stringify(result.map(x => x.toJSON()), null, 4));
});
The code above logs as expected (as I would expect, at least):
[
{
"id": 1,
"name": "name test",
"createdAt": "2018-03-11T03:04:28.657Z",
"updatedAt": "2018-03-11T03:04:28.657Z",
"Cars": [
{
"id": 1,
"make": "make test",
"model": "model test",
"createdAt": "2018-03-11T03:04:28.802Z",
"updatedAt": "2018-03-11T03:04:28.802Z",
"DriverCar": {
"color": "black",
"createdAt": "2018-03-11T03:04:28.961Z",
"updatedAt": "2018-03-11T03:04:28.961Z",
"driverId": 1,
"carId": 1
}
}
]
}
]
Note that there is no secret. Observe that the extra attribute that you're looking for, color, comes nested in the query result, not in the same nesting level of the Car or Driver. This is the correct behavior of Sequelize.
Make sure you can run this code and get the same result I do. My version of Node is different but I doubt that could be related to anything. Then, compare my code to your code and see if you can figure out what is causing you problems. If you need further help, feel free to ask in a comment :)
A note about many-to-many relationships with extra fields
Since I stumbled myself upon problems with this, and this is related to your situation, I thought I should add a section in my answer alerting you to the "trap" of setting up an overcomplicated many-to-many relationship (it's a lesson that I learned myself after struggling for a while).
Instead of repeating myself, I will just add a brief quote of what I said in Sequelize Issue 9158, and add links for further reading:
Junction tables, the tables that exist in relational databases to represent many-to-many relationships, initially have only two fields (the foreign keys of each table defining the many-to-many relationship). While it's true that it's possible to define extra fields/properties on that table, i.e. extra properties for the association itself (as you put in the issue title), care should be taken here: if it's getting overcomplicated, it's a sign that you should "promote" your junction table to a full-fledged entity.
Further reading:
My own self-answered question involving an overcomplicated setup of many-to-many relationships in sequelize: FindAll with includes involving a complicated many-to-(many-to-many) relationship (sequelizejs)
And its sibling question: Is it OK to have a many-to-many relationship where one of the tables involved is already a junction table?
I am currently using StrongLoop as my API backend server and Mongodb as data storage engine.
Let's say there is a collection called article. It has two fields title, and content. And there are two frontend pages to display a list of articles and view a single article.
Obviously the data list page only need title field and the view page need both. Currently the GET method of StrongLoop API return all fields including content. It cost extra traffic. Is there any way that can just return specific field?
Mongodb support projection in find() method for this. How can I do the same thing by StrongLoop?
Have you taken a look at the filters offered. http://docs.strongloop.com/display/LB/Querying+models
Query for NodeAPI:
server.models.Student.findOne({where: {RFID: id},fields: {id: true,schoolId: true,classId: true}}, function (err, data) {
if (err)
callback(err);
else {
callback();
}
})
Query for RestAPI :
$http.get('http://localhost:3000/api/services?filter[fields][id]=true&filter[fields][make]=true&filter[fields][model]=true')
.then(function (response) {
}, function (error) {
});
You can use fields projections,
Sample Record:
{ name: 'Something', title: 'mr', description: 'some desc', patient: { name: 'Asvf', age: 20, address: { street: 1 }}}
First Level Projection:
model.find({ fields: { name: 1, description: 1, title: 0 } })
and I think Strong loop is not yet supporting for second-level object filter, does anyone know how to filter second-level object properties or is yet to implement?.
Second Level Projection: (Need help here)
Ex: 2
model.find({ fields: { name: 1, 'patient.name': 1, 'patient.age': 1, 'patient.address': 0 } })
// Which results { name } only
I have the following two models: User and Job.
Each user can have just one job.
The user.attributes and job.attributes look like these (1):
(1)
user.attributes = {
id: 1,
name: 'barName',
job_id: 5
}
job.attributes = {
id: 5,
name: 'fooJob'
}
Let's suppose I want to make a relation between these two models:
The foreign key should be job_id
(2)
User = Backbone.ModelRelation.extend({
relations: [
{
type: Backbone.HasOne,
key: 'job_id',
keyDestination: 'job',
relatedModel: User
}
]
});
Using the code (2) the result will be:
(3)
user.attributes = {
id: 1,
name: 'barName',
job: job.attributes
}
As you can see the job_id from user.attributes is lost.
So, if I make a PUT request to the server, the server complains about the missing job_id attribute.
Any ideas how can I fix (3) in order to keep the job_id in user.attributes like (4)?
(4)
user.attributes = {
id: 1,
name: 'barName',
job_id: 5
job: job.attributes
}
Reference:
Paul Uithol - Backbone-relational
The workaround for me was to change the way the server reads the JSON posed.
So the server would read {user:{job:{id:1}}} rather than {user:{job_id:1}}
Note that we include a sub-object with an id attribute rather than use the job_id flat attribute.
Depending on which server side framework this can be configured.