Sequelize: Returning a single COUNT entry instead of every row in a nested include - postgresql

I'm trying to grab the total number of rows (count) from a nested include in Sequelize. I've followed countless questions on here, but can't seem to get it right.
Models:
Brand
BrandProfile (alias: brand_profile)
BrandPageView (alias brand_page_view)
Relationships:
// Brand
Brand.hasOne(models.BrandProfile, {
foreignKey: 'brand_id',
as: 'brand_profile'
})
// BrandProfile
BrandProfile.belongsTo(models.Brand, {
foreignKey: 'brand_id',
})
BrandProfile.hasMany(models.BrandPageView, {
foreignKey: 'brand_id',
as: 'brand_page_views'
})
// BrandPageView
BrandProfile.belongsTo(models.BrandProfile, {
foreignKey: 'brand_id',
})
Now when I try to normally run my query like so:
const { count, rows } = await Brand.findAndCountAll({
include: [
{
model: BrandProfile,
as: 'brand_profile',
include: [
{
model: BrandPageView,
as: 'brand_page_views',
},
],
},
],
})
It returns the following:
{
id: 1,
created_at: '2020-12-26T20:42:19.930Z',
updated_at: '2020-12-29T20:46:58.918Z',
deleted_at: null,
name: 'Test brand',
slug: 'test-brand',
status: 'disabled',
brand_profile: {
created_at: '2020-12-26T20:42:19.931Z',
about: [ [Object], [Object], [Object], [Object] ],
cleaned_about: null,
subtitle: 'subtitle test',
photo: '1609477287152.jpeg',
photo_config: { scale: '1.00' },
id: 1,
updated_at: '2021-01-01T05:01:27.414Z',
brand_id: 1,
brand_page_views: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object]
]
},
}
As you can see, brand.brand_profile.brand_page_views has a list of all objects. Now I just want to return the count, so I'm using the following query:
const { count, rows } = await Brand.findAndCountAll({
include: [
{
model: BrandProfile,
as: 'brand_profile',
include: {
model: BrandPageView,
as: 'brand_page_views',
attributes: [],
},
},
],
attributes: {
include: [
[
Sequelize.fn('COUNT', Sequelize.col('brand_profile.brand_page_views.brand_id')),
'brand_page_views_count',
],
],
},
group: ['brand.id']
})
I get this error:
'missing FROM-clause entry for table "brand"'
The SQL it outputs is:
SELECT "Brand"."id", "Brand"."created_at", "Brand"."updated_at", "Brand"."deleted_at", "Brand"."name", "Brand"."slug", "Brand"."status", COUNT("brand_profile->brand_page_views"."brand_id") AS "brand_profile.brand_page_views.count", "brand_profile"."created_at" AS "brand_profile.created_at", "brand_profile"."about" AS "brand_profile.about", "brand_profile"."cleaned_about" AS "brand_profile.cleaned_about", "brand_profile"."subtitle" AS "brand_profile.subtitle", "brand_profile"."photo" AS "brand_profile.photo", "brand_profile"."email" AS "brand_profile.email", "brand_profile"."photo_config" AS "brand_profile.photo_config", "brand_profile"."id" AS "brand_profile.id", "brand_profile"."updated_at" AS "brand_profile.updated_at", "brand_profile"."brand_id" AS "brand_profile.brand_id" FROM "brands" AS "Brand" LEFT OUTER JOIN "brand_profile" AS "brand_profile" ON "Br2021-01-05 01:32:26 [sequelize] INFO Executing (default): SELECT "Brand"."id", "Brand"."created_at", "Brand"."updated_at", "Brand"."deleted_at", "Brand"."name", "Brand"."slug", "Brand"."status", COUNT("brand_profile->brand_page_views"."brand_id") AS "brand_profile.brand_page_views.count", "brand_profile"."created_at" AS "brand_profile.created_at", "brand_profile"."about" AS "brand_profile.about", "brand_profile"."cleaned_about" AS "brand_profile.cleaned_about", "brand_profile"."subtitle" AS "brand_profile.subtitle", "brand_profile"."photo" AS "brand_profile.photo", "brand_profile"."email" AS "brand_profile.email", "brand_profile"."photo_config" AS "brand_profile.photo_config", "brand_profile"."id" AS "brand_profile.id", "brand_profile"."updated_at" AS "brand_profile.updated_at", "brand_profile"."brand_id" AS "brand_profile.brand_id" FROM "brands" AS "Brand" LEFT OUTER JOIN "brand_profile" AS "brand_profile" ON "Brand"."id" = "brand_profile"."brand_id" LEFT OUTER JOIN "brand_page_views" AS "brand_profile->braand"."id" = "brand_profile"."brand_id" LEFT OUTER JOIN "brand_page_views" AS "brand_profile->brand_page_views" ON "brand_profile"."id" = "brand_profile->brand_page_views"."brand_id" WHERE "Brand"."id" = 1 GROUP BY "brand"."id";
nd_page_views" ON "brand_profile"."id" = "brand_profile->brand_page_views"."brand_id" WHERE "Brand"."id" = 1 GROUP BY "brand"."id";
I've honestly tried this so many ways, added brand_profile->brand_page_views.brand_id, brand_profile.brand_id to the group, but to no avail.

Sequelize was not designed to support a wide variety of SQL aggregations. So in your case you should use sequelize.literal with a subquery to count child records:
attributes: {
include: [
[Sequelize.literal('(select COUNT(*) from brand_page_views where brand_page_views.brand_id=brand_profile.id)'),
'brand_page_views_count'],
],
},
Don't forget to remove group: ['brand.id']

Related

Sequelize Assosiative aggrigate function error

Error: SequelizeDatabaseError: column "receiving_product.id" must appear in the GROUP BY clause or be used in an aggregate function
Error: SequelizeDatabaseError: column "receiving_product.id" must appear in the GROUP BY clause or be used in an aggregate function
Error: SequelizeDatabaseError: column "receiving_product.id" must appear in the GROUP BY clause or be used in an aggregate function
Error: SequelizeDatabaseError: column "receiving_product.id" must appear in the GROUP BY clause or be used in an aggregate function
const singleRP = await db.receiving_product.findOne({
include: [
{
model: db.purchase_order,
as: "purchase_order",
include: [
{ model: db.vendor, as: "vendor" },
{
model: db.po_productlist,
as: "product_items",
attributes: [
"id",
"purchase_order_id",
"product_id",
"quantity",
"price",
"totalPrice",
"created_by",
"updated_by",
"tenant_id",
"createdAt",
"updatedAt",
[
db.sequelize.fn(
"SUM",
db.sequelize.col("purchase_order.product_items.receivinghistory.received_quantity")
),
"received_quantity",
],
],
include: [
{ model: db.product, as: "product" },
{
model: db.received_product,
as: "receivinghistory",
attributes: [
[
db.sequelize.fn(
"SUM",
db.sequelize.col("purchase_order.product_items.receivinghistory.received_quantity")
),
"received_quantity",
],
],
include: {
model: db.user,
as: "received_by",
},
},
{
model: db.product_serial,
as: "serials",
required: false,
where: {
rec_prod_id: id,
},
},
],
},
],
},
{
model: db.user,
as: "added_by",
include: {
model: db.role,
as: "roles",
},
},
],
where: {
[Op.and]: [
{
id,
tenant_id: TENANTID,
},
],
},
group: ['receiving_product.id']
});

Multiple Model Count Sequelize

I have a Model associated with two more models by hasMany.
Need a count of both Model's ids
Model.findAndCountAll({
attributes: {
include: [
[sequelize.fn('COUNT', sequelize.col('IncludeModel1.id')), 'includeModel1Count'],
[sequelize.fn('COUNT', sequelize.col('IncludeModel2.id')), 'IncludeModel2Count']
]
},
include: [
{ model: IncludeModel2, attributes: [] },
{
model: IncludeModel1,
attributes: [],
required: true
}
],
subQuery: false,
limit: this.details.limit || 10,
offset: this.details.offset || 0,
group: ['Model.id']
})
Gives Wrong Count
Can you show me the right way?
Thank You

Sequelize - How return rows only if included model has at least one record

I want to this query in Sequelize:
const results = await WorkflowAssignation.findAndCountAll(
{
limit: limit,
offset: offset,
where:
{
userId: userIds,
status: status
},
include: [
{
model: WorkflowStepAssignation,
include: [
{ model: WorkflowStepAssignationHasUser, include: [ users ]}
]
},
{ model: users }
],
distinct: true,
order: [ [ 'createdAt', 'DESC'], [ { model: WorkflowStepAssignation, as: "steps" }, 'stepNumber', 'ASC' ] ]
});
The problem is that I need to return only WorkflowAssignation if the WorkflowStepAssignationHasUser table contain at least 1 record with the value passed as parameter for userid.
if I used a where in this line { model: WorkflowStepAssignationHasUser, include: [ users ]} it will still return all the WorkflowAssignation with only the steps that has the userId value and that's not what I want.
It is like I want to return all the WorkflowAssignation including all the tables but only if in WorkflowStepAssignation -> WorkflowStepAssignationHasUser has the users in the parameters.
Relations are:
WorkflowAssignation
has Many
WorkflowStepAssignation
has Many
WorkflowStepAssignationHasUser (has a userId column)
add required: true
const results = await WorkflowAssignation.findAndCountAll(
{
limit: limit,
offset: offset,
where:
{
userId: userIds,
status: status
},
include: [
{
model: WorkflowStepAssignation,
include: [
{ model: WorkflowStepAssignationHasUser, include: [ users ]}
]
},
{ model: users }
],
distinct: true,
required: true,
order: [ [ 'createdAt', 'DESC'], [ { model:
WorkflowStepAssignation, as: "steps" }, 'stepNumber', 'ASC' ] ]
});

How to remove hasMany id attribute from sequelize join?

I am attempting to join two tables defined below in a one-to-many relationship. One Characteristic to many ReviewCharacteristics. Using the following findAll function I am attempting to avg the values in the ReviewCharacteristics table. I have verified the necessary SQL command that should function as expected. Sequelize is including ReviewCharateristic.id in the selected attributes which is causing my grouping to fail.
Is there a way to specify just the avg value and not include the id from the ReviewCharacteristic table in the selected attributes?
exports.Characteristic = db.define('characteristic', {
product_id: {
type: DataTypes.INTEGER,
allowNull: false
},
name: {
type: DataTypes.STRING(7),
allowNull: false
}
}, { underscored: true });
exports.ReviewCharacteristic = db.define('review_characteristic', {
value: {
type: DataTypes.INTEGER,
allowNull: false
}
}, { underscored: true });
exports.Characteristic.hasMany(exports.ReviewCharacteristic, { as: 'rc' });
exports.ReviewCharacteristic.belongsTo(exports.Characteristic);
Characteristic.findAll({
attributes: ['name', 'id'],
where: {
product_id: req.query.product_id
},
group: 'characteristic.id',
include: [{
model: ReviewCharacteristic,
as: 'rc',
attributes: [[Sequelize.fn('AVG', Sequelize.col('rc.value')), 'value']],
required: true
}]
});
I have also tried specifying attributes: ['characteristic.name', 'characteristic.id']
Current:
SELECT
"characteristic"."name",
"characteristic"."id",
"rc"."id" AS "rc.id",
AVG("rc"."value") AS "rc.value"
FROM "characteristic" AS "characteristic"
INNER JOIN "review_characteristic" AS "rc"
ON "characteristic"."id" = "rc"."characteristic_id"
WHERE "characteristic"."product_id" = '18078'
GROUP BY "characteristic"."id";
Expected:
SELECT
"characteristic"."name",
"characteristic"."id",
AVG("rc"."value") AS "rc.value"
FROM "characteristic" AS "characteristic"
INNER JOIN "review_characteristic" AS "rc"
ON "characteristic"."id" = "rc"."characteristic_id"
WHERE "characteristic"."product_id" = '18078'
GROUP BY "characteristic"."id";
If anyone happens to stumble across this question and is dealing with the same issue here is the solution I found.
Characteristic.findAll({
attributes: ['name', 'id', [Sequelize.fn('AVG', Sequelize.col('rc.value')), 'value']],
where: {
product_id: req.query.product_id
},
group: 'characteristic.id',
include: [{
model: ReviewCharacteristic,
as: 'rc',
attributes: [],
required: true
}]
});
The attributes must be specified as an empty array for the joined table in order to stop the addition of the ReviewCharateristic.id attribute in my case.

Return updated values from sequelize raw query

I have an operation that uses a raw query to update the database, i'd like to get the rows affected by the query as the output. Is this possible?
const op = (replacements, pgdb) {
const query = q(replacements);
const args = [query, { replacements, type: Sequelize.QueryTypes.UPDATE, raw: true }];
const results = await pgdb.query(...args);
console.log(results);
return results;
};
Postgresql can return records with RETURNING.
UPDATE T
SET ...
WHERE ...
RETURNING *
Yes you can. I'm using Sequelize with a PostgreSQL database. My specs are as follows:
"pg": "^7.12.1"
"sequelize": "^5.21.2"
"sequelize-cli": "^5.5.1"
I ran an update query in the following code block:
const query = `UPDATE bookings b
SET "isPaid" = :isPaid,
"paymentType" = :paymentType
FROM trips t,
requests r
WHERE "b"."tripId" = t.id
AND r.id = "t"."requestId"
AND r.id = :requestId
AND "r"."userId" = :userId
RETURNING b.id, "isPaid", "paymentType"`;
return db.sequelize.query(
query, {
replacements: {
isPaid, paymentType, requestId, userId
}
}
);
I logged the result:
console.log('resultFromQuery', resultFromQuery);
and got the following:
resultFromQuery [
[
{ id: 7, isPaid: true, paymentType: 'paypal' },
{ id: 8, isPaid: true, paymentType: 'paypal' }
],
Result {
command: 'UPDATE',
rowCount: 2,
oid: null,
rows: [ [Object], [Object] ],
fields: [ [Field], [Field], [Field] ],
_parsers: [ [Function: parser], [Function: parser], [Function: parser] ],
_types: TypeOverrides { _types: [Object], text: {}, binary: {} },
RowCtor: null,
rowAsArray: false
}
]
I was then able to get the rowCount as follows:
console.log('resultFromQuery', resultFromQuery[1].rowCount);
I was also able to get the updated rows themselves as follows (you can select the entire row like you did with '*' or specify the fields you want):
console.log('resultFromQuery', resultFromQuery[0]);