apply count on inner join in sql using sequelize - sql

If i have Posts model and Likes model that PostsLikes model has postID as foreign key
How to create orm sequelize query to get post by id and number of likes on it

You should use a subquery in attributes options like this:
const posts = await Posts.findByPk(postId, {
attributes: {
include: [[Sequelize.literal('SELECT COUNT(*) FROM PostsLikes where PostsLikes.postID = Posts.ID)'), 'LikesCount']]
}
})

Related

How to count number of user id in a list without duplicate in sequelize [duplicate]

I am trying to get a distinct count of a particular column using sequelize. My initial attempt is using the 'count' method of my model, however it doesn't look like this is possible.
The DISTINCT feature is needed because I am joining other tables and filtering the rows of the parent based on the related tables.
here's the query I would like:
SELECT COUNT(DISTINCT Product.id) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
using the following query against my Product model:
Product.count({
include: [{model: models.Vendor, as: 'vendor'}],
where: [{ 'vendor.isEnabled' : true }]
})
Generates the following query:
SELECT COUNT(*) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
UPDATE: New version
There are now separate distinct and col options. The docs for distinct state:
Apply COUNT(DISTINCT(col)) on primary key or on options.col.
You want something along the lines of:
MyModel.count({
include: ...,
where: ...,
distinct: true,
col: 'Product.id'
})
.then(function(count) {
// count is an integer
});
Original Post
(As mentioned in the comments, things have changed since my original post, so you probably want to ignore this part.)
After looking at Model.count method in lib/model.js, and tracing some code, I found that when using Model.count, you can just add any kind of aggregate function arguments supported by MYSQL to your options object. The following code will give you the amount of different values in MyModel's someColumn:
MyModel.count({distinct: 'someColumn', where: {...}})
.then(function(count) {
// count is an integer
});
That code effectively generates a query of this kind: SELECT COUNT(args) FROM MyModel WHERE ..., where args are all properties in the options object that are not reserved (such as DISTINCT, LIMIT and so on).
The Sequelize documentation on count links to a count method that doesn't let you specify which column to get the count of distinct values:
Model.prototype.count = function(options) {
options = Utils._.clone(options || {});
conformOptions(options, this);
Model.$injectScope(this.$scope, options);
var col = '*';
if (options.include) {
col = this.name + '.' + this.primaryKeyField;
expandIncludeAll.call(this, options);
validateIncludedElements.call(this, options);
}
Utils.mapOptionFieldNames(options, this);
options.plain = options.group ? false : true;
options.dataType = new DataTypes.INTEGER();
options.includeIgnoreAttributes = false;
options.limit = null;
options.offset = null;
options.order = null;
return this.aggregate(col, 'count', options);
};
Basically SELECT COUNT(DISTINCT(*)) or SELECT COUNT(DISTINCT(primaryKey)) if you've got a primary key defined.
To do the Sequelize equivalent of SELECT category, COUNT(DISTINCT(product)) as 'countOfProducts' GROUP BY category, you'd do:
model.findAll({
attributes: [
'category',
[Sequelize.literal('COUNT(DISTINCT(product))'), 'countOfProducts']
],
group: 'category'
})
Looks like this is now supported in Sequelize versions 1.7.0+.
the count and findAndCountAll methods of a model will give you 'real' or 'distinct' count of your parent model.
I was searching for SELECT COUNT(0) query for sequelize, below is the answer for that.
let existingUsers = await Users.count({
where: whereClouser,
attributes: [[sequelize.fn('COUNT', 0), 'count']]
});
This helped me to get distinct count from another table rows,
dataModel.findAll({
attributes: {
include: [[Sequelize.literal("COUNT(DISTINCT(history.data_id))"), "historyModelCount"]]
},
include: [{
model: historyModel, attributes: []
}],
group: ['data.id']
});
Ref 1, Ref 2.
With respect to your question in order to get the distinct counts of products based on the id of product
you just need to pass the key 'distinct' with value 'id' to your count object , Here is the example
To generate this sql query as you asked
SELECT COUNT(DISTINCT(`Product`.`id`)) as `count`
FROM `Product`
LEFT OUTER JOIN `Vendor` AS `vendor` ON `vendor`.`id` = `Product`.`vendorId`
WHERE (`vendor`.`isEnabled`=true );
Add 'distinct' key in your Sequelize query
Product.count({
include: [{model: models.Vendor, as: 'vendor'}],
where: [{ 'vendor.isEnabled' : true }],
distinct: 'id' // since count is applied on Product model and distinct is directly passed to its object so Product.id will be selected
});
This way of using 'distinct' key to filter out distinct counts or rows , I tested in Sequelize Version 6.
Hope this will help you or somebody else!

Knex : update a slice of randomly selected records

I have a table that stores gifts :
export interface Gift {
id: number
type : string
claim_status: string
user_id?: number
}
When a user claims one or multiple gifts, i want to select randomly some gifts and update them with the user_id. I tried using the knex limitfunction but it doesn't work for updating.
export const claimGifts = async (
user : User,
numberToClaim: number,
trx: Knex.Transaction
) => {
const gifts = await db<Gift>('gift')
.where({claim_status : 'available'})
// limit the amount of updated to numberToClaim be slicing randomly
.update({user_id : user.id, claim_status : 'claimed'}, '*')
.transacting(trx)
return gifts
}
Any idea ?
You should first construct a SQL query, and then convert it to Knex usage.
I would use nested query for selecting random entries, something like:
Update gift set user_id = 'MY_USER_ID' Where id IN (Select inner_g.id from gifts as inner_g where claim_status='available' Order by RAND() Limit 3)
When it converted to Knex, it looks like:
const gifts = await db<Gift>('gift')
.update({
user_id: user.id,
claim_status: 'claimed',
})
.whereIn(
'id',
db('gift as inner_g')
.columns('inner_g.id')
.where({ claim_status: 'available' })
.orderBy(db.raw('RAND()') as any)
.limit(3)
);

NestJS TypeORM pagination with many-to-many relationship

I have method in my product service like the one below. I'm filtering the data and using nestjs-typeorm-paginate to paginate, but it doesn't work properly (with a page size of 10 it returns 4-5 records depending on the number of related rooms. Without using room relation everything works great)
async findAllPaginated(
options: IPaginationOptions,
filters?: any,
): Promise<any> {
const queryBuilder = this.productRepository.createQueryBuilder('product');
queryBuilder.leftJoinAndSelect('product.category', 'category');
/* Room uses many to many relation */
queryBuilder.leftJoinAndSelect('product.room', 'room');
if (filters.category) {
queryBuilder.andWhere(`category.id like '${filters.category}'`);
}
if (filters.room) {
queryBuilder.andWhere(`room.id like '${filters.room}'`);
}
if (filters.priceMin) {
queryBuilder.andWhere(`product.price >= ${filters.priceMin}`);
}
if (filters.priceMax) {
queryBuilder.andWhere(`product.price <= ${filters.priceMax}`);
}
if (filters.promoPriceMin) {
queryBuilder.andWhere(`product.promoPrice >= ${filters.promoPriceMin}`);
}
if (filters.promoPriceMax) {
queryBuilder.andWhere(`product.promoPrice <= ${filters.promoPriceMax}`);
}
if (filters.sortField) {
queryBuilder.orderBy(filters.sortField, filters.sortDirection);
}
return paginate<Product>(queryBuilder, options);
}
Generated SQL looks like this:
SELECT
`product`.`id` AS `product_id`,
`product`.`shortenUrl` AS `product_shortenUrl`,
`product`.`name` AS `product_name`,
`product`.`price` AS `product_price`,
`product`.`promoPrice` AS `product_promoPrice`,
`product`.`deliveryCost` AS `product_deliveryCost`,
`product`.`promoEndDate` AS `product_promoEndDate`,
`product`.`description` AS `product_description`,
`product`.`amount` AS `product_amount`,
`product`.`photo` AS `product_photo`,
`product`.`width` AS `product_width`,
`product`.`height` AS `product_height`,
`product`.`depth` AS `product_depth`,
`product`.`colorCode` AS `product_colorCode`,
`product`.`created` AS `product_created`,
`product`.`updated` AS `product_updated`,
`product`.`deletedAt` AS `product_deletedAt`,
`product`.`categoryId` AS `product_categoryId`,
`product`.`ordersId` AS `product_ordersId`,
`category`.`id` AS `category_id`,
`category`.`name` AS `category_name`,
`category`.`icon` AS `category_icon`,
`category`.`created` AS `category_created`,
`category`.`updated` AS `category_updated`,
`category`.`deletedAt` AS `category_deletedAt`,
`category`.`groupId` AS `category_groupId`,
`room`.`id` AS `room_id`,
`room`.`shortenUrl` AS `room_shortenUrl`,
`room`.`name` AS `room_name`,
`room`.`icon` AS `room_icon`,
`room`.`created` AS `room_created`,
`room`.`updated` AS `room_updated`,
`room`.`deletedAt` AS `room_deletedAt`
FROM
`product` `product`
LEFT JOIN `category` `category` ON
`category`.`id` = `product`.`categoryId` AND(`category`.`deletedAt` IS NULL)
LEFT JOIN `room_products_product` `room_product` ON
`room_product`.`productId` = `product`.`id`
LEFT JOIN `room` `room` ON
`room`.`id` = `room_product`.`roomId` AND(`room`.`deletedAt` IS NULL)
WHERE
`product`.`deletedAt` IS NULL
ORDER BY
`product`.`name` ASC
And it returns duplicated data with diffrent rooms data:
The data returned to the client looks fine, with no duplicates with the correct link to the rooms, but with incorrect pagination.
How can I paginate correctly in this case? Is this at all possible?
I would appreciate any help :)

how to make a subquery using the query builder of laravel

I want to do the following mysql query with the query builder of Laravel 7:
SELECT r.id, (SELECT date FROM posts as pp where r.id = pp.request_id
ORDER BY STR_TO_DATE(SUBSTRING(pp.date, 1, 19),'%Y-%m-%dT%H:%i:%s') DESC limit 1), r.delivery_mode FROM requests AS r
LEFT JOIN posts AS p ON r.id = p.request_id
group by r.id;
However unfortunately I don't understand how to obtain the latests post date for every request using the query builder of Laravel. I suppose I should do a subquery as in the query above. This is my code:
$query = DB::table('requests');
$query->select(DB::raw('DISTINCT(requests.id)'),'requests.delivery_mode',DB::raw('posts.date'));
$query->leftJoin('posts', 'requests.id', '=', 'posts.request_id');
$requests_list = $query->get();
In the table posts, the date is a string in this format: 2020-04-16T12:46:33+02:00. So I used that complicated functions because I want to see only the latest date post grouped by id_request, that is the foreign key of the table posts connected with the primary key id of the table requests. A request can have many posts.
Can help?
PS:
I found the solution:
$query = DB::table('requests');
$query->select(DB::raw('DISTINCT(requests.id)'),'requests.delivery_mode',DB::raw("(SELECT date FROM posts as pp where requests.id = pp.request_id ORDER BY STR_TO_DATE(SUBSTRING(pp.date, 1, 19),'%Y-%m-%dT%H:%i:%s') DESC limit 1) as lastPostDate"));
$query->leftJoin('posts', 'requests.id', '=', 'posts.request_id');
$requests_list = $query->get();
Probably it isn't a pretty solution but it works!.
Request Model
public function posts()
{
return $this->hasMany(Post::class);
}
Post Model
public function request()
{
return $this->belongsTo(Request::class);
}
Controller
public function index()
{
$date = Post::has("request")
->orderBy(...) // add your order by condition here
->limit(1)
->pluck("date");
$requests = Request::with("posts")
->where("date", $date)
->get();
return $requests;
}

hasmany in express js not working with sequelize

I have created models Skills and Members
Member Table:
id username password created
1 gagandeep 99703dd91da5b0cabf496d49af0ab03c2dfe7788 2017-08-14 05:59:46
Skills Table:
id member_id skill
1 1 programming
2 1 music
Code:
Model
var Member=Seq.define('members',{}, {});
var Skills=Seq.define('skills',{}, {});
Member.hasMany(Skills,{as: 'skills', through: 'skills', foreignKey:'member_id'});
Call:
router.route('/test').get(function(req, resp){
Member.findAll({include: [{model: Skills}]},{
raw:true
}).success(onSuccess).error(onError);
function onSuccess(data){
resp.json(data);
}
function onError(data){
resp.json(data);
}
});
But Node js showing error as below:
Error: skills is not associated to members! at DAOFactory.validateIncludedElement (C:\Users\promatics\Desktop\netpar\node_modules\sequelize\lib\dao-factory.js:1476:13) at DAOFactory.validateIncludedElements (C:\Users\promatics\Desktop\netpar\node_modules\sequelize\lib\dao-factory.js:1388:59) at DAOFactory.module.exports.DAOFactory.findAll (C:\Users\promatics\Desktop\netpar\node_modules\sequelize\lib\dao-factory.js:458:34) at Object.handle (C:\Users\promatics\Desktop\netpar\demo.js:72:9) at next_layer (C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\route.js:103:13) at Route.dispatch (C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\route.js:107:5) at C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\index.js:195:24 at Function.proto.process_params (C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\index.js:251:12) at next (C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\index.js:189:19) at next (C:\Users\promatics\Desktop\netpar\node_modules\express\lib\router\index.js:166:38)
You don't actually need that through statement. You only use through when you have an association between two tables through a junction table. What I would do here is something like...
var Member = Seq.define('members', {
// Some attributes...
});
var Skills = Seq.define('skills', {
// Some attributes...
});
Member.hasMany(Skills, {
as: 'skills',
// Remove `through`!
foreignKey: 'member_id'
});
// Want to query for all members with the skills joined?
Member.findAll({
include: {
model: Skills,
as: 'skills'
}
});
That should get you the results you are looking for. Each member object will have reference to an inner skills key, which will be an array of skills associated to the user.
Good luck :)