Convert MySQL "where" to Sequelizejs "where" - javascript

I was trying to write "WHERE (CASE ... THEN ... ELSE ... END) > 0" to sequelize v3.33 but couldn't find the solution yet.
Had tried sequelize.literal('...') but didn't work.
Using "HAVING" is one of the solutions but it's no good for performance-wise for large data extraction and it's twice as slow.
This is just an example of MySQL code but pretty much close to what I want to achieve.
SELECT
(CASE `a`.`fee` IS NULL
THEN `a.b`.`fee`
ELSE `a`.`fee`
END) AS `_fee`
FROM `a`
WHERE
(CASE `a`.`fee` IS NULL
THEN `a.b`.`fee`
ELSE `a`.`fee`
END) > 0 AND
(created_at > currentDate
AND
created_at < futureDate)
I want to convert this to sequelize. Below is as far as I can go, I don't know how to add that case closure.
models.a.findAll({
...
where: {
created_at: { $gt: startDate, $lt: endDate }
}
})
*** Don't mind about created_at, it's just an example.

You can use sequelize.where and sequelize.literal for that :
where:{
$and : [
{ created_at: { $gt: startDate, $lt: endDate } },
sequelize.where(sequelize.literal('(CASE `a`.`fee` IS NULL THEN `a.b`.`fee` ELSE `a`.`fee` END)') , { $gt : 0 } )
]
}
Note : this might not work as alias a. of the table might be diff, you can debug and change as per your query

Related

How to find and replace all 'null' date occurences in MongoDB collection?

Also replacing other formats later, such as: finding all dates with 'dd/mm/YYYY' format and changing them to ISO 'YYYY-mm-dd' format.
But as for the 'null' issue, this is the .js I am trying to run in MongoDB (NoSQLBooster):
use sms
db.collection1.find({
"FirstDate":null
})
.projection({})
//.sort({ _id: -1 })
//.limit(1000)
.forEach(function(doc) {
var date = doc.FirstDate
if (date == null) {
date = ''
}
})
And all I'm getting is "undefined" results. What could the problem be?
You can use update with {multi: true} to ensure all values that match are updated (not only the first one)
db.collection.update({
FirstDate: null
},
{
$set: {
FirstDate: ""
}
},
{
multi: true
})
Example here

Mongoose: Reduce from many queries to one

I currently have a setup with multiple requests to the database in a for-loop.
// week is a moment-range object
for (const day of week.by('day')) {
// get start and end of day
const startDay = day.startOf('day').toDate();
const endDay = day.endOf('day').toDate();
// I would like to reduce this query to one
const data = await Model.find({
$or: [
{
$and: [{ start: { $gte: startDay } }, { start: { $lt: endDay } }],
},
{
$and: [{ end: { $gte: startDay } }, { end: { $lt: endDay } }],
},
],
})
.sort({ start: 'asc' })
.select('_id someref')
.populate('someref', 'name');
console.log(data);
}
This is not very efficient and therefore I would like to reduce it to one query, grouped by the current return of data.
I've tried already to prepare the find parameter in the for-loop but didn't get far. Any hints would be very much appreciated.
Your query seems to find records that either start or end in a given day. As week is a range of consecutive days, this really translates to a query for records that either start in that week, or end in that week.
So you could remove the for loop, and define startDay and endDay as the start/end of the week, as follows:
const startDay = week.start.startOf('day').toDate();
const endDay = week.end.endOf('day').toDate();
The rest of your code can remain as it is. Just remove the line with for and the corresponding ending brace.
One difference though is that you wouldn't get any duplicates any more. In your current code you would get records that both start and end in the week (but not on the same day) twice. That will not happen with this code.

MongoDB aggregate lookup not working with multiple parameters

I am pulling data from comments table and it works. I want to do a join equivalent with performance in mind on the users collection to get details about the user who commented.
Here is my code where I use Next js. I added the aggregate/ lookup and now I dont get anything back.
const from = req.query.from ? new Date(req.query.from) : new Date();
const postId = req.query.by;
const comments = await req.db
.collection('commentsPosts')
.find({
commentedAt: {
$lte: from,
},
...(postId && { postId }),
})
.sort({ commentedAt: -1 })
.limit(parseInt(req.query.limit, 10) || 10)
//This part is not working and breaking the whole query
.aggregate({
$lookup:{
from:"users",
localField:"createdBy",
foreignField:"_id",
as:"commenterDetails"
}
})
/////////////
.toArray();
res.send({ comments });
Edit
Updated as per the answer below but still no results
.collection('commentsPosts')
.aggregate(
[
{$match:{
commentedAt: {
$lte: from,
},
...(postId && { postId }),
}
.sort({ commentedAt: -1 })
.limit(parseInt(req.query.limit, 10) || 10)
},
{$lookup:
{
from:"users",
localField:"createdBy",
foreignField:"_id",
as:"commenterDetails"
}
}
]
).toArray();
you do not need to do a find() when you are planning to use aggregate()
Remove the find() and introduce a $match in aggregate pipeline.
db.comments.aggregate(
[
{$match:{commentedAt:{$gt:QUERY_CLAUSE_HERE}},
{$lookup:{...}}
]
)

Sequelize cast column to integer in where clause

Trying to cast a column inside a where clause to search for a query against. Have a difficult time finding reliable documentation on how to do this. Any ideas?
return await User.findAndCountAll({
where: {
[Op.or]: {
'email': { [Op.iLike]: `%${query}%` },
'$id::text$': { [Op.iLike]: `%${query}%` } // id is int but needs to be string for searching
}
},
limit,
offset: page * limit,
order: [[sortBy, sortOrder.toUpperCase()]],
raw: true,
logging: console.log
});
I think this is what you searching for:
where: {
[Op.or]: [
{email: {[Op.iLike]: `%${query}%`}},
sequelize.where(
sequelize.cast(sequelize.col('User.id'), 'varchar'),
{[Op.iLike]: `%${query}%`}
),
],
},
As #leogoesger's answer didn't work for JSONB fields in Postgres, I tried this:
where: {
["data.createdAt::timestamp"]: {
[Op.lt]: "now() - interval '3 days'"
}
}
Lo and behold, it produced:
WHERE CAST(("User"."data"#>>'{createdAt}') AS TIMESTAMP) < now() - interval '3 days'
Using Sequelize 5.1.0.
This worked for me:
Notification.findOne({ where: {
[Op.and]: [
sequelize.literal("cast(notify_data as CHAR) = "+game_id),
{user: players[i].user, notify_type: type.id}
]
}});
Basically, sequelize.literal returns a string rather than a key-value pair, so I did a work around using Op.and which can take both strings and objects in its array
order: [sequelize.col('id')],
where:
sequelize.where(
sequelize.cast(sequelize.col('id'), 'varchar'),
{ [Op.iLike]: `%${filter}%` },
),
// matches all rows using id and fetching results in order (casts integer column to string)

MongoDB: adding sort() to query ruins result set for $and query

I've stumbled upon some very strange behavior with MongoDB. For my test case, I have an MongoDB collection with 9 documents. All documents have the exact same structure, including the fields expired_at: Date and location: [lng, lat].
I now need to find all documents that are not expired yet and are within a bounding box; I show match documents on map. for this I set up the following queries:
var qExpiry = {"expired_at": { $gt : new Date() } };
var qLocation = { "location" : { $geoWithin : { $box : [ [ 123.8766, 8.3269 ] , [ 122.8122, 8.24974 ] ] } } };
var qFull = { $and: [ qExpiry, qLocation ] };
Since the expiry date is long in the past, and when I set the bounding box large enough, the following queries give me all 9 documents as expected:
db.docs.find(qExpiry);
db.docs.find(qLocation);
db.docs.find(qFull);
db.docs.find(qExpiry).sort({"created_at" : -1});
db.docs.find(qLocation).sort({"created_at" : -1});
Now here's the deal: The following query returns 0 documents:
db.docs.find(qFull).sort({"created_at" : -1});
Just adding sort to the AND query ruins the result (please note that I want to sort since I also have a limit in order to avoid cluttering the map on larger scales). Sorting by other fields yield the same empty result. What's going on here?
(Actually even stranger: When I zoom into my map, I sometimes get results for qFull, even with sorting. One could argue that qLocation is faulty. But when I only use qLocation, the results are always correct. And qExpiry is always true for all documents anyway)
You may want to try running the same query using the aggregation framework's $match and $sort pipelines:
db.docs.aggregate([
{ "$match": qFull },
{ "$sort": { "created_at": -1 } }
]);
or implicitly using $and by specifiying a comma-separated list of expressions as in
db.docs.aggregate([
{
"$match": {
"expired_at": { "$gt" : new Date() },
"location" : {
"$geoWithin" : {
"$box" : [
[ 123.8766, 8.3269 ],
[ 122.8122, 8.24974 ]
]
}
}
}
},
{ "$sort": { "created_at": -1 } }
]);
Not really sure why that fails with find()
chridam suggestion using the aggregation framework of MongoDB proved to be the way to go. My working query now looks like this:
db.docs.aggregate(
[
{ $match : { $and : [qExpiry, qLocation]} },
{ $sort: {"created_at": -1} }.
{ $limit: 50 }.
]
);
Nevertheless, if any can point out way my first approach did not work, that would be very useful. Simply adding sort() to a non-empty query shouldn't suddenly return 0 documents. Just to add, since I still tried for a bit, .sort({}) return all documents but was not very useful. Everything else failed including .sort({'_id': 1}).

Categories

Resources