In my node js app, I have a situation where I have to get the current time and check whether it is between the start time and end time in the db table. First, I submit some data with start time and end time to the database. Then, I have a scheduler where in each minute, it checks the current time and check whether its between the start time and end time in db table to continue. I cannot figure out how to achieve this with sequelize. I couldn't find the solution with between condition in sequelize. It only helps if we are sending start time and end time, and date we should compare is in db as I understood. Not the one I need.
I have this sample code I wrote to make it understandable but it's syntactically wrong.
const data = dataSource.DBTableName.findAll({
attributes: [
// db columns
],
where: {
trackStartTime < currentTime, // This current time should be between trackStartTime and trackEndTime in db
trackEndTime > currentTime
}
});
If someone can help me how to achieve it with sequlize, it's much appreciated.
You should do this
const data = dataSource.DBTableName.findAll({
attributes: [
// db columns
],
where: {
[Op.and]: [
{
trackStartTime: {
[Op.lte]: currentTime
}
},
{
trackEndTime: {
[Op.gte]: currentTime
}
}
]
}
});
If you would like to get the time from the database for NOW() and then return results from the database where that value is BETWEEN the trackStartTime and trackEndTime you can use sequelize.where() to generate the condition, sequelize.fn() by passing in 'NOW', and the Op.between operator for the arguments, passing in via sequelize.col() for each column.
const { Op } = Sequelize;
const data = dataSource.DBTableName.findAll({
attributes: [ /* db columns */ ],
where: {
sequelize.where(sequelize.fn('NOW'), {
[Op.between]: [
sequelize.col('trackStartTime'),
sequelize.col('trackEndTime')
],
}),
},
});
SELECT ? FROM `DBTableName`
WHERE NOW() BETWEEN `trackStartTime` AND `trackEndTime`
Related
I try to use a Sequelize function to collect only table content that is older than today. But only for one day, not two days. As sample today
today: 2022/08/23
values only for: 2022/08/22 but not for 2022/08/21 and older.
For this is use:
checkDateBack: async function() {
return await dayins.findAll({
where: {
added_at: {
[Op.gte]: Sequelize.literal("DATE_SUB(CURDATE(), INTERVAL 1 DAY)"),
}
},
raw: true,
})
},
I have deleted all entries for 2022/08/22 in my temporary debug table but the result is 2022/08/21. In my idea of the function 2022/08/21 and older will get ignored.
So if I delete all entries for 2022/08/22 I expect an empty result.
Anything I did wrong with the function so it collects also 2 days old?
As I understood you need yesterday data, you need to get date range, use moment js for date
let date = new Date(); //suppose today is 2022/08/23
let endDate = moment(date).startOf("day").toString(); // starting of the day 12 am of(2022/08/23 )
let startDate = moment(date).subtract(1, "days").startOf("day").toString(); // starting of the day 12 am of(2022/08/22 )
// use [Op.between] insate of [Op.gte]
return await dayins.findAll({
where: {
added_at: {
[Op.between]: [startDate, endDate],
}
},
raw: true,
})
// it will return all data of 2022/08/22
// for sequelize createdAt if dont have any custome date fild
createdAt: {
[Op.between]: [startDate, endDate],
},
I have a column time_scheduled of type Sequelize.DATE which stores the scheduled time of events.
I am tying to create a Sequelize query where I can find all events that fall between a date range and time range. The SQL query for the same is below.
SELECT * FROM events where time_scheduled between '2021/10/01' and '2021/10/10' and time_scheduled::timestamp::time between '12:00' and '15:00';
Sample output for the same
id
time_scheduled
4d543320-4d23-46d2-8a54-fbb13a1251d0
2021-10-05 14:30:00+00
d6640e70-f873-436c-a59d-5a4c4fc655b7
2021-10-06 12:02:49.441+00
b11481b2-ffdd-413e-81af-f83df756bcc9
2021-10-06 13:55:36.62+00
53447517-f226-407f-94f4-63ddc8c17d9c
2021-10-07 13:59:48.123+00
f0344678-11eb-4422-9d23-43e8d5320f55
2021-10-07 14:14:13.647+00
You can use Op.between along with Sequelize.where and Sequelize.cast to achieve what you want:
const records = await Events.findAll({
where: {
[Op.and]: [{
time_scheduled: {
[Op.between]: ['2021/10/01', '2021/10/10']
}
},
Sequelize.where(Sequelize.cast(Sequelize.col('time_scheduled'), 'time'), '>=', '12:00'),
Sequelize.where(Sequelize.cast(Sequelize.col('time_scheduled'), 'time'), '<=', '15:00')
]
}
});
I have a Mongoose abTest document that has two fields:
status. This is a string enum and can be of type active, inactive or draft.
validCountryCodes. This is an array of strings enums (GB, EU, AU etc). By default, it will be empty.
In the DB, at any one time, I only want there to be one active abTest for each validCountryCode so I'm performing some validation prior to creating or editing a new abTest.
To do this, I've written a function that attempts to count the number of documents that have a status of active and that contain one of the countryCodes.
The function will then return if the count is more than one. If so, I will throw a validation error.
if (params.status === 'active') {
const activeTestForCountryExists = await checkIfActiveAbTestForCountry(
validCountryCodes,
);
if (params.activeTestForCountryExists) {
throw new ValidationError({
message: 'There can only be one active test for each country code.',
});
}
}
const abTest = await AbTest.create(params);
checkIfActiveAbTestForCountry() looks like this:
const checkIfActiveAbTestForCountry = async countryCodes => {
const query = {
status: 'active',
};
if (
!countryCodes ||
(Array.isArray(countryCodes) && countryCodes.length === 0)
) {
query.validCountryCodes = {
$eq: [],
};
} else {
query.validCountryCodes = { $in: [countryCodes] };
}
const count = await AbTest.countDocuments(query);
return count > 0;
};
The count query should count not only exact array matches, but for any partial matches.
If in the DB there is an active abTest with a validCountryCodes array of ['GB', 'AU',], the attempting to create a new abTest with ['GB' should fail. As there is already a test with GB as a validCountryCode.
Similarly, if there is a test with a validCountryCodes array of ['AU'], then creating a test with validCountryCodes of ['AU,'NZ'] should also fail.
Neither is enforced right now.
How can I do this? Is this possible write a query that checks for this?
I considered iterating over params.validCountryCodes and counting the docs that include each, but this seems like bad practice.
take a look at this MongoDB documantation.
As I understood what you need is to find out if there is any document that contains at least one of the specified countryCodes and it has active status. then your query should look like this:
{
status: 'active',
$or: [
{ validCountryCodes: countryCodes[0] },
{ validCountryCodes: countryCodes[1] },
// ...
]
}
note that counting documents is not an efficient manner to check if a document exists or not, instead use findOne with only one field being projected.
You are using the correct mongo-query for your requirement. Can you verify the actual queries executed from your application is the same? Check here
{ status: 'active', validCountryCodes: { $in: [ countryCodes ] } }
For eg; below query :
{ status: 'active', validCountryCodes: { $in: ['GB' ] } }
should match document :
{ status: 'active', validCountryCodes: ['AU','GB'] }
const A = mongoose.Schema({
...
bs: [B.schema],
...
});
So basically i have two schemas, and one is subdocument of another.
From my datatable i get params. like filter, page limit, page, sort...
What i need to do is, to create query that will with _id from A schema get all his B schemas and always sort, limit, skip, filter with params. that i sent
I tried something like this
b = await A.find({'_id' : idA},
{ 'bs' :
{ $slice: [ offset * limit, limit ]
}
});
And it's working but i can't still figure out how to filter and sort.
So if somebody have some idea welcome to share.
P.S Sorry for bad english
Regards,
Salesh
What you're trying to do is not find A documents that fulfill your array criteria, but to modify the results to accommodate to your needs. You can do this with two approaches, depending on where you want the processing to be done:
1. Use MongoDB Aggregation. The processing is done in the DB.
The aggregation pipeline is a series of steps you determine that documents go through being queried and transformed.
A rough untested (and probably syntactically wrong) example would be:
A.aggregate([
{ $match: { _id: "id" }},
{ $project: {
bs: {
$filter: { input: "$bs" , as: "filteredBs" , cond: { /* conditions object */} }},
}
},
{ $slice: ["$filteredBs", offset * limit, limit ] }
/* ... */
]);
2. Get the document by Id and process the array on your server.
Here you're just limited by javascript and its array capabilites.
const found = A.findById('id');
const bs = A.bs.filter( /* filter function */ ).slice() // ... whatever you want.
A.bs = bs;
return A;
I use Node.js and MongoDB with monk.js and i want to do the logging in a minimal way with one document per hour like:
final doc:
{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1 }, {action: action2, count: 27 }, {action: action3, count: 5 } ] }
the complete document should be created by incrementing one value.
e.g someone visits a webpage first this hour and the incrementation of action1 should create the following document with a query:
{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1} ] }
an other user in this hour visits an other webpage and document should be exteded to:
{ time: YYYY-MM-DD-HH, log: [ {action: action1, count: 1}, {action: action2, count: 1} ] }
and the values in count should be incremented on visiting the different webpages.
At the moment i create vor each action a doc:
tracking.update({
time: moment().format('YYYY-MM-DD_HH'),
action: action,
info: info
}, { $inc: {count: 1} }, { upsert: true }, function (err){}
Is this possible with monk.js / mongodb?
EDIT:
Thank you. Your solution looks clean and elegant, but it looks like my server can't handle it, or i am to nooby to make it work.
i wrote a extremly dirty solution with the action-name as key:
tracking.update({ time: time, ts: ts}, JSON.parse('{ "$inc":
{"'+action+'": 1}}') , { upsert: true }, function (err) {});
Yes it is very possible and a well considered question. The only variation I would make on the approach is to rather calculate the "time" value as a real Date object ( Quite useful in MongoDB, and manipulative as well ) but simply "round" the values with basic date math. You could use "moment.js" for the same result, but I find the math simple.
The other main consideration here is that mixing array "push" actions with possible "updsert" document actions can be a real problem, so it is best to handle this with "multiple" update statements, where only the condition you want is going to change anything.
The best way to do that, is with MongoDB Bulk Operations.
Consider that your data comes in something like this:
{ "timestamp": 1439381722531, "action": "action1" }
Where the "timestamp" is an epoch timestamp value acurate to the millisecond. So the handling of this looks like:
// Just adding for the listing, assuming already defined otherwise
var payload = { "timestamp": 1439381722531, "action": "action1" };
// Round to hour
var hour = new Date(
payload.timestamp - ( payload.timestamp % ( 1000 * 60 * 60 ) )
);
// Init transaction
var bulk = db.collection.initializeOrderedBulkOp();
// Try to increment where array element exists in document
bulk.find({
"time": hour,
"log.action": payload.action
}).updateOne({
"$inc": { "log.$.count": 1 }
});
// Try to upsert where document does not exist
bulk.find({ "time": hour }).upsert().updateOne({
"$setOnInsert": {
"log": [{ "action": payload.action, "count": 1 }]
}
});
// Try to "push" where array element does not exist in matched document
bulk.find({
"time": hour,
"log.action": { "$ne": payload.action }
}).updateOne({
"$push": { "log": { "action": payload.action, "count": 1 } }
});
bulk.execute();
So if you look through the logic there, then you will see that it is only ever possible for "one" of those statements to be true for any given state of the document either existing or not. Technically speaking, the statment with the "upsert" can actually match a document when it exists, however the $setOnInsert operation used makes sure that no changes are made, unless the action actually "inserts" a new document.
Since all operations are fired in "Bulk", then the only time the server is contacted is on the .execute() call. So there is only "one" request to the server and only "one" response, despite the multiple operations. It is actually "one" request.
In this way the conditions are all met:
Create a new document for the current period where one does not exist and insert initial data to the array.
Add a new item to the array where the current "action" classification does not exist and add an initial count.
Increment the count property of the specified action within the array upon execution of the statement.
All in all, yes posssible, and also a great idea for storage as long as the action classifications do not grow too large within a period ( 500 array elements should be used as a maximum guide ) and the updating is very efficient and self contained within a single document for each time sample.
The structure is also nice and well suited to other query and possible addtional aggregation purposes as well.