Sequelize WHERE NOT EXISTS() - javascript

I have a many to many relations that looks like this:
var Genres = db.define('Movie', {
name: {
type: Sequelize.STRING(100),
allowNull: false
},
description: {
type:Sequelize.STRING(),
allowNull: true
},
thumbnail: {
type: Sequelize.BLOB(),
allowNull: true
},
urlposter: {
type: Sequelize.STRING(245),
allowNull: true
}
});
var Users = db.define('User', {
username: {
type: Sequelize.STRING(25),
allowNull: false
},
password: {
type: Sequelize.STRING(25),
allowNull: false
}
});
Movies.belongsToMany(Users, {through: UM, foreignKey: 'Id_Movies'});
Users.belongsToMany(Movies, {through: UM, foreignKey: 'Id_Users'});
what I will do is return all Movies that have no link to a specific user
this is my SQL query i want to achive:
SELECT m.*
FROM Movies m
WHERE NOT EXISTS(SELECT NULL
FROM Users_Movies sc
WHERE sc.Id_Movies = m.id AND sc.Id_Users = 1)
This is the closest I've got but this just return all movies that have a link u user with ID 1
Movies.findAll({
include: [
// {
// model:Genres,
// through:{attributes:[]}
// },
{
model:Users,
through:{attributes:[]},
where: {id: 1},
required: true,
}],
})
.then(function(movies){
done(null, movies);
})
.catch(function(error){
done(error, null);
});
But I want to invert it.
right now my only option is to make 2 queries one with all movies and one with all movies that have a link and loop through the list and remove them from the first list.. this is not pretty code.

I know that I'm late on the matter, but I think that using the literal 'users.id IS NULL' in the query's 'where' will do the trick:
Movie.findAll({
where: sequelize.literal("users.id IS NULL"),
include: [
{
model: Users,
through: { attributes: [] }
}],
})
.then(function (movies) {
done(null, movies);
})
.catch(function (error) {
done(error, null);
});
This solutions is based on the following Github issue: https://github.com/sequelize/sequelize/issues/4099

Use rawQuery
const projects = await sequelize.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
});

Related

How to create with association in Sequelize

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.

How to create nested requests with sequelize between two tables

I have two tables in a sequelize backend I am building. The first table is groups and the second table is members. I want to:
In the end, I want to submit a an api request that contains a users Id. It will then grab all rthe records from the members table and for each record, grab the group which is referenced as a foreign key in the members table. I then want to return the group records to the frontend.
Is there a way to grab the foreign key records directly through the foreign key or do I need to make two requests?
Here is the code I have:
router:
router.route('/user_groups/:userId')
.get(memberController.getUserMember)
Controller:
getUserMember: (req, res) => {
let group_list = [];
let user_id = req.params.userId
Member.findAll({ where: { userId: user_id } })
.then((response) => {
for(let i = 0; i < response.length; i++){
Group.findByPk(response[i]['groupId'])
.then((group) => {
console.log(JSON.stringify(group))
group_list.push(group)
})
.catch((err) => {
console.log('Getting Group by Id error: ' + JSON.stringify(err))
})
}
console.log(group_list)
res.status(200).send(data)
})
.catch((err) => {
console.log('Getting member by Id error: ' + JSON.stringify(err))
})
},
the first request gets all of the member records containing the userId
the second request within the then function will cycle through the members and grab the
groups for each record based on its Id through the foreign key
each of the records are that is returned from the group request is supposed to be stored in an array and then the array will be returned at the end....
The objects are not storing in the array and the array is not being returned. not sure what to do.
model member:
const Member = database.define(
"member",
{
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true},
balance: {type: seq.FLOAT(9, 2), allowNull: true, defaultValues: '0.00',
validate: {isFloat: true}
},
open_tabs: {type: seq.INTEGER, allowNull: false, defaultValues: '0',
validate: {isInt: true}
},
reference: {type: seq.STRING, allowNull: false,
validate: {isAlphanumeric: true}
},
admin: {type: seq.BOOLEAN, allowNull: false, defaultValues: false,
validate: {isIn: [['true', 'false']]}
},
active: {type: seq.BOOLEAN, allowNull: false, defaultValues: false,
validate: {isIn: [['true', 'false']]}
},
},
{
createdAt: seq.DATE,
updatedAt: seq.DATE,
}
)
Member.belongsTo(Group)
Member.belongsTo(User)
model group:
const Group = database.define(
"group",
{
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true},
name: {type: seq.STRING, allowNull: false,
validate: {}
},
description: {type: seq.TEXT, allowNull: true,
validate: {}
},
// icon: {type: seq.STRING, allowNull: false,
// validate: {}
// },
members: {type: seq.INTEGER, allowNull: false,
validate: {isInt: true}
},
reference: {type: seq.STRING, allowNull: false,
validate: {isAlphanumeric: true}
},
active: {type: seq.BOOLEAN, allowNull: false, defaultValues: false,
validate: {isIn: [['true', 'false']]}
},
},
{
createdAt: seq.DATE,
updatedAt: seq.DATE,
}
)
Group.belongsTo(User, {as: "Host"})
The basic problem is the loop will complete before all the Group.findByPk() requests are completed and once loop completes the array is sent....but the array is still empty!
Map an array of the Group.findByPk() promises instead and use Promise.all() to do the send() after all those promises have resolved
Something like:
getUserMember: (req, res) => {
let user_id = req.params.userId
Member.findAll({ where: { userId: user_id } })
.then((response) => {
const groupPromises = response.map(member=>{
return Group.findByPk(member['groupId']).then(groups=>{
member.groups = groups;
return member;
});
});
return Promise.all(groupPromises).then(data=>res.status(200).send(data))
})
.catch((err) => {
console.log('Something went wrong in one of the steps ' + JSON.stringify(err))
})
},
I'm assuming you want an array that includes the original member objects and have assigned the groups to each of those objects as property groups

Associate in Sequelize not working as intended

I am trying to associate two tables in Sequelize but I am getting the SequelizeEagerLoadingError that one table is not associated to another despite trying all the available fixes on this platform.
I have two tables, User and Item.
User (user.js)
const User = dbconnection.sequelize.define('users', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
name: {
type: Sequelize.STRING(80),
allowNull: false
},
email: {
type: Sequelize.STRING(120),
allowNull: false,
unique: true
},
dob: {
type: Sequelize.DATEONLY,
allowNull: false
},
password: {
type: Sequelize.STRING(256),
allowNull: false
}
});
User.associate = models => {
User.hasMany(models.Item, { as: 'items',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
//console.log('Table created!')
});
module.exports = {
User
};
Item (item.js)
const Item = dbconnection.sequelize.define('items', {
id: { type: Sequelize.INTEGER, unique: true, autoIncrement: true, primaryKey: true},
item: {
type: Sequelize.STRING(80),
allowNull: true
},
item_type: {
type: Sequelize.STRING(10),
allowNull: false
},
comment: {
type: Sequelize.STRING(1000),
allowNull: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'users', key: 'id' }
},
});
Item.associate = models => {
Item.belongsTo(models.User, { as: 'users',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
// console.log('Table created!')
})
});
module.exports = {
Item
};
User hasMany(Item) while Item belongsTo(User) as shown above.
However, when I make a query to the Item table (as below),
const usersdb = require('./userdb')
const itemsdb = require('./itemdb')
class ItemsController {
static async getAllItems(req, res, next) {
try{
let allitems = await itemsdb.Item.findAll({
include: [{
model: usersdb.User
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;
I get the SequelizeEagerLoadingError that "users is not associated to items!"
I have tried all the available fixes including this and this among others but to no success.
I have finally found a workaround. First, I dropped the tables and discarded the model definitions. Second, I generated migrations and models using the sequelize model:create --name ModelName --attributes columnName:columnType command. I then used the generated models to associate the two tables just as I had done earlier. Lastly, I ran the sequelize db:migrate command to create the tables and on running the query, it worked!
Earlier, I was creating the models manually. I was also creating the tables using the sequelize.sync({force: false/true}) command after loading the models.
User Model (user.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes(120),
allowNull: false,
unique: true
},
dob: {
type: DataTypes.DATEONLY,
allowNull: false
},
password: {
type: DataTypes.STRING(256),
allowNull: false
}
}, {});
User.associate = function(models) {
User.hasMany(models.Item, {as: 'Item', foreignKey: 'user_id'})
};
return User;
};
Item model (item.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
item: {
type: DataTypes.STRING(80),
allowNull: true
},
item_type: {
type: DataTypes.STRING(10),
allowNull: false
},
comment: {
type: DataTypes.STRING(1000),
allowNull: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'User', key: 'id' }
}
}, {});
Item.associate = function(models) {
Item.belongsTo(models.User, { as: 'User',foreignKey: 'user_id' })
};
return Item;
};
Query (queryitem.js)
const Item = require('../models').Item
const User = require('../models').User
class ItemsController {
static async getAllItems() {
try{
let allitems = await Item.findAll({
include: [{
model: User,
as: 'User'
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;

SequelizeJS HasOne association error

I am relatively new to NodeJS and SequelizeJS and am facing a hasOne issue with a query I am building and I'd like to know your thoughts about this issue to find out where I gone wrong and the correct way to implement this query.
Association Here
The models where generated using sequelize-auto (pg-hstore).
Bloco Model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('bloco_condominio', {
id_bloco: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
id_condominio: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'condominio',
key: 'id_condominio'
}
},
nm_bloco: {
type: DataTypes.STRING,
allowNull: true
},
ic_status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "A"
}
}, {
tableName: 'bloco_condominio'
});
};
Apartamento Model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('apartamento', {
id_apartamento: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
id_condominio: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'condominio',
key: 'id_condominio'
}
},
nu_apto: {
type: DataTypes.STRING,
allowNull: true
},
id_bloco: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'bloco_condominio',
key: 'id_bloco'
}
},
ic_status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "A"
},
dt_incl: {
type: DataTypes.TIME,
allowNull: false,
defaultValue: sequelize.fn('now')
},
dt_ult_alt: {
type: DataTypes.TIME,
allowNull: false,
defaultValue: sequelize.fn('now')
}
}, {
tableName: 'apartamento'
});
};
Apartamento Service:
"use strict";
var model = require('../models');
var Utils = require('../utils/utils');
var service = {};
var Apartamento = model.apartamento;
var Bloco = model.bloco_condominio;
var Morador = model.morador;
var Pessoa = model.pessoa;
//Incluir relação OneToMany
Apartamento.hasMany(Morador, { as: "Moradores", foreignKey: 'id_apartamento' });
Morador.belongsTo(Apartamento, { foreignKey: 'id_apartamento' });
Morador.hasMany(Pessoa, { as: "Pessoa", foreignKey: 'id_pessoa' });
Pessoa.belongsTo(Morador, { foreignKey: 'id_pessoa' });
Bloco.hasMany(Apartamento, { as: "Bloco", foreignKey: 'id_bloco' });
Apartamento.hasMany(Bloco, { foreignKey: 'id_bloco' });
service.getApartamentoById = function(idApartamento) {
return Apartamento.findById(idApartamento, {
include: [
{ model: Morador, as: 'Moradores', include: [
{ model: Pessoa, as: 'Pessoa'}
]},
{ model: Bloco, as: 'Bloco' }
]
})
.then(function(data) {
return data;
})
.catch(function(err) {
throw 'Erro ao consultar apartamento por ID: ' + err.message + ' - Request: '+JSON.stringify(idApartamento);
});
};
I can perfectly retrieve the other hasMany associations, but still hasn't found a way to do so in the reverse way.
Do you guys have any idea of how I should approach this issue in the correct manner?
Thanks in advance for your help!
Best regards,
Enrico Bergamo
To make it simpler for me (only knowing English), I've grabbed the following from Google translate:
Pessoa: Person
Morador: Dweller
Bloco: Block
Apartmento: Apartment
So, Dweller can have many People, an Apartment can have many Dwellers and a Block can have many Apartments.
Your definition on the other models indicates they're all 1:m, so I followed that assumption for Apartments and Blocks.
With that in mind, the following should work.
Bloco.hasMany(Apartamento, { as: "Apartmento", foreignKey: 'id_bloco' });
Apartamento.belongsTo(Bloco, { foreignKey: 'id_bloco' });
Note: I've changed the as: "Bloco" to as: "Apartmento" and the second hasMany to belongsTo. This might be where your issues were coming from.
Edit: The method to access the Apartments that belong to a Block is:
bloco.getApartmento(options)
I have this working with this promise chain:
Bloco.create()
.then(block => {
return Promise.all([
block,
Apartamento.bulkCreate([{
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {}])
])
})
.spread((bloco, apartment) => {
return bloco.getApartamento()
})
.then(apartments => {
console.log(apartments.length); --> Logs 4 which matches the bulk create.
})
If I've misinterpreted, and it should be an n:m relationship (Apartments/Blocks), then you should use belongsToMany on each model and identify the through option.
Bloco.belongsToMany(Apartamento, {
as: "Apartmentos",
foreignKey: 'id_bloco',
through: "BlocoAparmento"
});
Apartamento.belongsToMany(Bloco, {
as: "Blocos",
foreignKey: 'id_apartmento',
through: "BlocoAparmento"
});
This will create an n:m joining table called "BlockApartmento". If you define that model, and use the model instead of the string, you'll have complete control over the models settings.
This will give you the Bloco.getApartmentos( methods as well as opposite (Apartmento.getBlocos() along with setAssociation, addAssoc... etc
http://docs.sequelizejs.com/manual/tutorial/associations.html#belongs-to-many-associations

Sequelize include an attribute from junction table

I'm building a shop, where in the database i have orders and items. Here's the code for the models:
Item:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Item = sequelize.define('item', {
image: {
type: Sequelize.STRING,
allowNull: false
},
itemName: {
type: Sequelize.STRING,
allowNull: false,
field: 'item_name'
},
price: {
type: Sequelize.INTEGER,
allowNull: false
}
})
module.exports = Item
Order:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Order = sequelize.define('order', {
orderNumber: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
shop: {
type: Sequelize.INTEGER
},
location: {
type: Sequelize.STRING
}
})
module.exports = Order
They are related through belongs to many:
Item.belongsToMany(Order, {through: OrderItem})
Order.belongsToMany(Item, {through: OrderItem})
The OrderItem has an additional field, 'count', which i need to return:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var OrderItem = sequelize.define('OrderItem', {
count: Sequelize.INTEGER
})
module.exports = OrderItem
However, when i try to include the OrderItem model, it doesn't work. No errors, nothing. The query just doesn't return:
Order.findAll({
where: {
userId: userId
},
include: [{
model: Item,
include: [OrderItem]
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
How to get what i need from sequeilize?
You may try something like this:
Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']} // this may not be needed
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
Also, your models must have a right naming strategy. E.g. :
Item - must have itemId field instead of itemNumber,
Order - must have orderId as primary field
OrderItem's structure:
var OrderItem = sequelize.define('OrderItem', {
orderId: Sequelize.INTEGER,
itemId: Sequelize.INTEGER,
count: Sequelize.INTEGER
})
Another one is to use direct names of related fields and models when you use belongsToMany()
More here: http://docs.sequelizejs.com/en/latest/docs/associations/
Turns out that the OrderItem is already nested inside the Item object. However, this doesn't make a nice return format , so the question is still open.
I know this is year later, but yet..
You can use the property joinTableAttributes to get a junction table fields .
for example :
Order.findAll({
where: {
userId: userId
},
joinTableAttributes: ['count'],
include: [{
model: Item,
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
I am using Graphql and I had the same problem with the formatting. I did a small map after getting the result.
For example:
let orders = await Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']}
}]
});
let ordersWithCount = orders.map((o) => {
let orderWithCount = {};
orderWithCount = o;
orderWithCount.count = o.OrderItem.count;
return orderWithCount;
});

Categories