How to do this request using sequelize? - node.js

I would like to know the code to write to execute this request using sequelize:
select * from "Songs"
where Id in (
select "songId" from "Likes"
where "userId"=1
)

I finaly found by myself how to do it, here is my code :
const songs = await models.Songs.findAll({
attributes: ['id', 'title', 'imageUrl', 'nbListening'],
where: {
id: {
[Op.in]: sequelize.literal(
`(select "songId" from "Likes" where "userId"=${req.user.id})`
),
},
},

Related

Get number of associated elements in Sequelize

So I have a many-to-many relationship with Sequelize. This code gives me an array of all the categories associated with the Post. It works to get this data. However, if I would like to make that list of categories into just a single key value pair of how many categories instead of the categories. How could I do that?
return models.Post.findAndCountAll({
limit: limit,
offset: offset,
include: [{
model: models.Category,
as: 'categories',
required: false,
}],
})
For example this is the current output:
{
"id": 1,
"name": "foo",
"categories": [
{
"id": 1,
"name": "bar"
}
]
}
The desired output:
{
"id": 1,
"name": "foo",
"categories": 10
}
EDIT: As suggestions for fixing this I tried doing this:
return models.Post.findAndCountAll({
group: ['post.id'],
attributes: {
include: [[db.sequelize.fn("COUNT", db.sequelize.col("categories.id")), "categoriesCount"]]
},
limit: limit,
offset: offset,
include: [{
model: models.Category,
as: 'categories',
required: true,
attributes: []
}],
raw: true,
subQuery: false
})
But that just gives me the error:
{
"message": "invalid reference to FROM-clause entry for table \"post\""
}
This is basically what I want to get back, i wrote it in SQL and tried it:
SELECT
cp.category_id as category_id,
p.name as post_name,
COUNT(p.id) as num_categories
FROM
category c,
category_post cp
JOIN
post p ON p.id = cp.category_id
WHERE
p.id = cp.post_id AND
p.created_at >= '2022-01-26' and p.created_at <= '2022-05-02'
GROUP BY
cp.category_id,
post_name
ORDER BY
num_categories DESC
Generated SQL with Sequelize:
Executing (default): SELECT "post"."id", count("post"."id") AS "count" FROM "post" AS "post" INNER JOIN ( "category_post" AS "categories->categoryPost" INNER JOIN "category" AS "categories" ON "categories"."id" = "categories->categoryPost"."category_id") ON "post"."id" = "categories->categoryPost"."post_id" GROUP BY "post"."id";
Executing (default): SELECT "post"."id", "post"."name", COUNT("categories"."id") AS "categoryCount", "categories->categoryPost"."id" AS "categories.categoryPost.id", "categories->categoryPost"."category_id" AS "categories.categoryPost.category_id", "categories->categoryPost"."post_id" AS "categories.categoryPost.post_id" FROM "post" AS "post" INNER JOIN ( "category_post" AS "categories->categoryPost" INNER JOIN "category" AS "categories" ON "categories"."id" = "categories->categoryPost"."category_id") ON "post"."id" = "categories->categoryPost"."post_id" GROUP BY "post"."id" LIMIT 3 OFFSET 0;
My models look like the following:
Post(id, name, created_at, updated_at)
Category(id, name,)
PostCategory(id, post_id,category_id)
In my Post model:
static associate(models) {
this.belongsToMany(models.Category, {
through: models.CategoryPost,
as: 'posts',
foreignKey: 'category_id',
onDelete: 'CASCADE'
})
}
In my Category model:
static associate(models) {
this.belongsToMany(models.Post, {
through: models.CategoryPost,
as: 'categories',
foreignKey: 'post_id',
onDelete: 'CASCADE'
})
}
The generated SQL based on Emma's answer:
Executing (default): SELECT count("Post"."id") AS "count" FROM "Post" AS "Post" INNER JOIN ( "category_Post" AS "categories->categoryPost" INNER JOIN "category" AS "categories" ON "categories"."id" = "categories->categoryPost"."category_id") ON "Post"."id" = "categories->categoryPost"."Post_id";
Executing (default): SELECT "Post"."id", "Post"."name", (COUNT("categories"."id") OVER (PARTITION BY "Post"."id")::int) AS "categories" FROM "Post" AS "Post" INNER JOIN ( "category_Post" AS "categories->categoryPost" INNER JOIN "category" AS "categories" ON "categories"."id" = "categories->categoryPost"."category_id") ON "Post"."id" = "categories->categoryPost"."Post_id" LIMIT 3 OFFSET 0;
If Post is the parent and Category is the child and you want to find the number of categories for a given post... you can u se the following way..
return models.Post.findAll({
attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col("categories.id")), "cetegoryCount"]]
},
include: [{
model: models.Category,
as: 'categories'
}],
})
group in Postgres usually have some issues(Aggregate funcction issues).
Alternatively, you can use OVER PARTITION BY syntax which usually works in this situation.
const posts = await models.Post.findAndCountAll({
attributes: {
include: [[db.sequelize.literal('(COUNT("categories"."id") OVER (PARTITION BY "post"."id")::int)'), 'categories']]
},
limit: limit,
offset: offset,
include: [{
model: models.Category,
as: 'categories',
required: true,
attributes: [],
through: {
attributes: []
}
}],
raw: true,
subQuery: false
})
This should return something like this.
{
"result": {
"count": 2, // This count is for post
"rows": [
{
"id": 1,
"name": "post",
"categories": 2,
...
}
]
}
}

How to run AND and Or operator with Sequelize in Node.js

I am new to Node.js, am trying to run a 'And' and 'OR' operator in Sequelize, the MySQL query is this
SELECT * from User WHERE (role = 'INSTRUCTOR') AND ((pix <> null) OR (description <> null)) OEDER BY id DESC
The above MySQL query is what I want to run with Sequelize but it didn't work.
Below is my Sequelize code:
return await models.User.findAll({
where: {role: 'INSTRUCTOR'}, [Op.or]: [{pix: {[Op.ne]: null}}, {description: {[Op.ne]: null,}}], order: [['id', 'DESC']]
})
How can I run that query in Sequelize?
Formatting the code can sometimes help ...
Seems you had a misplaced }
return await models.User.findAll({
where: {
role: { [Op.eq]: 'INSTRUCTOR' },
[Op.or]: [
{ pix: { [Op.ne]: null} },
{ description: { [Op.ne]: null } }
]
},
order: [ ['id', 'DESC'] ]
})

unknown column in field list sequelize

I'm trying to perform the following query using Sequelize:
db.Post.findAll({
include: [
{
model: db.User,
as: 'Boosters',
where: {id: {[Op.in]: a_set_of_ids }}
},
{
model: db.Assessment,
as: 'PostAssessments',
where: {UserId: {[Op.in]: another_set_of_ids}}
}
],
attributes: [[db.sequelize.fn('AVG', db.sequelize.col('Assessments.rating')), 'average']],
where: {
average: 1
},
group: ['id'],
limit: 20
})
But I run to this error: "ER_BAD_FIELD_ERROR". Unknown column 'Assessments.rating' in 'field list', although I do have table "Assessments" in the database and "rating" is a column in that table.
My Post model looks like this:
const Post = sequelize.define('Post', {
title: DataTypes.TEXT('long'),
description: DataTypes.TEXT('long'),
body: DataTypes.TEXT('long')
}, {
timestamps: false
});
Post.associate = function (models) {
models.Post.belongsToMany(models.User, {as: 'Boosters', through: 'UserPostBoosts' });
models.Post.hasMany(models.Assessment, {as: 'PostAssessments'});
};
What am I doing wrong?
It seems like this problem surfaces when we have a limit in a find query where associated models are included (the above error doesn't show up when we drop the limit from the query). To solve that, we can pass an option subQuery: false to the find. (https://github.com/sequelize/sequelize/issues/4146)
This is the correct query in case anyone comes across the same problem:
db.Post.findAll({
subQuery: false,
include: [
{
model: db.User,
as: 'Boosters',
where: {id: {[Op.in]: a_set_of_ids }}
}
,{
model: db.Assessment,
as: 'PostAssessments',
where: {UserId: {[Op.in]: another_set_of_ids}}
}
],
having: db.sequelize.where(db.sequelize.fn('AVG', db.sequelize.col('PostAssessments.rating')), {
[Op.eq]: 1,
}),
limit: 20,
offset: 2,
group: ['Post.id', 'Boosters.id', 'PostAssessments.id']
})
Error is with this one :
models.sequelize.col('Assessments.rating'))
Change it to
models.sequelize.col('PostAssessments.rating')) // or post_assessments.rating
Reason : You are using the alias for include as: 'PostAssessments',.

Sequelize WHERE sequelize.fn(...) AND something='something' ordering issue

I have a Sequelize findOne function that looks to select a row where the given point intersects a polygon (col 'geom') AND status = 'active'.
var point = sequelize.fn('ST_GeomFromText', 'POINT(' + lng + ' ' + lat +')', 4326);
var intersects = sequelize.fn('ST_Intersects', sequelize.col('geom'), point);
GeoCounty.findOne({
attributes: ['id', 'name' ],
where: {
status: 'active',
$and: intersects
},
plain: true
})
As of right now, it works just fine. It produces SQL that looks like:
SELECT "id", "name" FROM "geocounty" AS "geocounty" WHERE "geocounty"."status" = 'active' AND (ST_Intersects("geom", ST_GeomFromText('POINT(-98.025006 43.714735)', 4326))) LIMIT 1;
What I really want is:
SELECT "id", "name" FROM "geocounty" AS "geocounty" WHERE (ST_Intersects("geom", ST_GeomFromText('POINT(-98.025006 43.714735)', 4326))) AND "geocounty"."status" = 'active' LIMIT 1;
which is to say the ST_Intersects clause comes first and the AND status='active' comes after.
My questions are:
1. Is there any sort of performance penalty for executing the query the first way which does work?
2. Is there a way to structure a where clause like this in Sequelize?
This does not work:
GeoCounty.findOne({
attributes: ['id', 'name' ],
where: {
intersects,
$and: {
status: 'active'
}
},
plain: true
})
It produces this SQL:
SELECT "id", "name" FROM "geocounty" AS "geocounty" WHERE "geocounty"."intersects" = ST_Intersects("geom", ST_GeomFromText('POINT(-98.025006 43.714735)', 4326)) AND ("geocounty"."status" = 'active') LIMIT 1;
There is no geocounty.intersects...
i stumbled over this post while searching for a similar problem and found a solution for me, that might help you with #2.
I wrapped the function call into an extra where. My code looks like this (works in NodeJs 10.9.0, Sequelize 4.38.0 on a MariaDB):
Cat.findOne({
where: {
color: 'red',
$and: sequelize.where(sequelize.fn('char_length', sequelize.col('cat_name')), 5)
}
});
SELECT id, cat_name, color FROM cat_table WHERE color = 'red' AND char_length(cat_name) = 5;
On your example it would look like this (not tested):
var intersects = sequelize.fn('ST_Intersects', sequelize.col('geom'), point);
GeoCounty.findOne({
attributes: ['id', 'name' ],
where: {
$and: sequelize.where(intersects, 1),
status: 'active'
},
plain: true
})

How to count a group by query in NodeJS Sequelize

In Rails I can perform a simple ORM query for the number of Likes a model has:
#records = Model
.select( 'model.*' )
.select( 'count(likes.*) as likes_count' )
.joins( 'LEFT JOIN likes ON model.id = likes.model_id' )
.group( 'model.id' )
This generates the query:
SELECT models.*, count(likes.*) as likes_count
FROM "models" JOIN likes ON models.id = likes.model_id
GROUP BY models.id
In Node Sequelize, any attempt at doing something similar fails:
return Model.findAll({
group: [ '"Model".id' ],
attributes: ['id', [Sequelize.fn('count', Sequelize.col('"Likes".id')), 'likes_count']],
include: [{ attributes: [], model: Like }],
});
This generates the query:
SELECT
Model.id,
count(Likes.id) AS likes_count,
Likes.id AS Likes.id # Bad!
FROM Models AS Model
LEFT OUTER JOIN Likes
AS Likes
ON Model.id = Likes.model_id
GROUP BY Model.id;
Which generates the error:
column "Likes.id" must appear in the GROUP BY clause or be used in an aggregate function
It's erroneously selecting likes.id, and I have no idea why, nor how to get rid of it.
This sequelize github issue looks totally like your case:
User.findAll({
attributes: ['User.*', 'Post.*', [sequelize.fn('COUNT', 'Post.id'), 'PostCount']],
include: [Post]
});
To resolve this problem we Need to upgrade to latest version of sequelize and include raw = true,
Here is How I had done after lot of iteration and off-course googling.
getUserProjectCount: function (req, res) {
Project.findAll(
{
attributes: ['User.username', [sequelize.fn('COUNT', sequelize.col('Project.id')), 'ProjectCount']],
include: [
{
model: User,
attributes: [],
include: []
}
],
group: ['User.username'],
raw:true
}
).then(function (projects) {
res.send(projects);
});
}
where my reference models are
//user
var User = sequelize.define("User", {
username: Sequelize.STRING,
password: Sequelize.STRING
});
//project
var Project = sequelize.define("Project", {
name: Sequelize.STRING,
UserId:{
type:Sequelize.INTEGER,
references: {
model: User,
key: "id"
}
}
});
Project.belongsTo(User);
User.hasMany(Project);
after migration ORM create 'Users' & 'Projects' table into my postgres server.
Here is SQL Query by ORM
SELECT
"User"."username", COUNT("Project"."id") AS "ProjectCount"
FROM
"Projects" AS "Project"
LEFT OUTER JOIN "Users" AS "User" ON "Project"."UserId" = "User"."id"
GROUP BY
"User"."username";
What worked for me counting column A and grouping by column B
const noListingsPerRetailer = Listing.findAll({
attributes: [
'columnA',
[sequelize.fn('COUNT', sequelize.col('columnB')), 'labelForCountColumn'],
],
group:["columnA"]
});

Resources