Date range filter and One-to-Many relation query syntax - prisma

I am trying to filter booked rooms to find all available rooms:
model Room {
id Int #id #default(autoincrement())
roomNumber String #unique
hotel String
bookings Booking[]
}
model Booking {
id Int #id #default(autoincrement())
hotel String
startDate DateTime
endDate DateTime
roomId Int
room Room #relation(fields: [roomId], references: [id])
##map("bookings")
}
I don't get the syntax of a prisma findMany query that would check, given two booking dates, all available rooms.
The mathematical logic to find if a room is not available is (i.e. if one booking interval crosses or contains the dates given) :
(startDate <= dto.startDate && dto.startDate < endDate) ||
(startDate < dto.endDate && dto.endDate <= endDate) ||
(dto.startDate <= startDate && dto.endDate >= endDate)
I would appreciate any help as I don't understand quite well the order of logic filters in prisma...

You can use none:
prisma.room.findMany({
where: {
bookings: {
none: {
// ...
},
},
},
});
and specify the condition for a booking that would case a conflict (i.e. make the room not available) or use every:
prisma.room.findMany({
where: {
bookings: {
every: {
//...
},
},
},
});
and specify the condition that bookings have to meet to not cause a conflict.
The condition you've specified seems unnecessary complex to me. Every booking that meets this condition should be a conflict:
(dto.startDate <= endDate && dto.endDate >= startDate)
(might need some adjustment depending on how you want to treat edge cases)
All together:
prisma.room.findMany({
where: {
bookings: {
none: {
endDate: { lte: dto.startDate },
startDate: { gte: dto.endDate },
},
},
},
});
Just in case you want to specify more complex conditions using OR you can do like this:
prisma.room.findMany({
where: {
bookings: {
none: {
OR: [
{
// (startDate <= dto.startDate && dto.startDate < endDate)
startDate: { lte: dto.startDate },
endDate: { gt: dto.startDate },
},
{
// (startDate < dto.endDate && dto.endDate <= endDate)
startDate: { lt: dto.endDate },
endDate: { gte: dto.endDate },
},
{
// (dto.startDate <= startDate && dto.endDate >= endDate)
startDate: { gte: dto.startDate },
endDate: { lte: dto.endDate },
},
],
},
},
},
});

Related

Conditional query Prisma assigning a value using date

I want to find the menus type provided by the restaurant (breakfast type) according to the time now, and the hours to display is on ShowingHours model
I created a Menu model
model Menu {
id String #id #default(cuid())
type String?
branchId String
branch Branch #relation(fields: [branchId], references: [id])
menuCategories MenuCategory[]
ShowingHours ShowingHours? #relation(fields: [showingHoursId], references: [id])
showingHoursId String?
}
and ShowingHours
model ShowingHours {
id String #id #default(cuid())
fromHour Int?
fromMinute Int?
toHour Int?
toMinute Int?
allDay Boolean
menus Menu[]
}
let timeNow = new Date().getHours();
I want to make a condition that if the value of timeNow for example is 9 am, then query a menu that has a inbetween timenow hours from "fromHour" to "toHour"
My approach:
let menuType = await db.menu.findMany({
// between fromhour tohour
where: {
branchId: branchId,
//I WOULD LIKE TO MAKE A CONDITION LIKE: if timeNow >= fromHour && timenow < toHour then fetch
ShowingHours: {
OR: [
{
fromHour: {
in:
}
}
]
}
},
})
You can use the lt and gt comparison operators to form the query.
It could look something like this:
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient({
log: [{ level: 'query', emit: 'stdout' }],
});
async function main() {
let timeNow = new Date().getHours();
await prisma.menu.findMany({
where: {
branchId: '1',
ShowingHours: {
AND: [
{
fromHour: {
lt: timeNow,
},
},
{
toHour: {
gt: timeNow,
},
},
],
},
},
});
}
main()
.catch((e) => {
throw e;
})
.finally(async () => {
await prisma.$disconnect();
});

Count or Include filtered relations prisma

I am currently stuck on a problem with my prisma queries.
I have an asset which has a 1 to Many relationship to views. I am trying to perform a findMany() on assets which returns either;
The asset with a list of views created within the last day
Or the asset with a count of views created in the last day
Finally I need to be able to orderBy this count or the count of views in my include statement. (this is what I am stuck on)
return await prisma.asset.findMany({
take: parseInt(pageSize),
skip: (pageSize * pageNumber),
include: {
_count: {
select: {
views: true
},
},
views: {
where: {
createdAt: dateFilter
},
},
likes: {
where: {
createdAt: dateFilter
}
},
transactions: true,
},
orderBy: { views: { _count: 'desc' } }
My queries does correctly return only views in my date range but how do I go about ordering the assets based on the count of these views. I have been stuck for quite some time on this. My raw SQL is not strong enough to write it from scratch at the moment.
If anyone has any ideas, thanks.
Will something like this work?
// First we group the views, with pagination
const groupedViews = await prisma.view.groupBy({
take: 10,
skip: 0,
by: ['postId'],
where: { createdAt: dateFilter },
_count: { postId: true },
orderBy: { _count: { postId: 'desc' } },
});
// Fetch the posts from the grouped views
const _posts = await prisma.post.findMany({
where: {
id: { in: groupedViews.map(({ postId }) => postId) },
},
include: {
_count: { select: { views: true } },
views: { where: { createdAt: dateFilter } },
},
});
// Map the fetched posts back for correct ordering
const posts = groupedViews.map(({ postId }) =>
_posts.find(({ id }) => id === postId)
);
Model:
model Post {
id String #id #default(cuid())
views View[]
}
model View {
id String #id #default(cuid())
createdAt DateTime #default(now())
postId String
post Post #relation(fields: [postId], references: [id])
}
This uses 2 separate queries, but does not require raw sql

count number of order every hour with loopback4

Salem,
I have tried to return the number of order per hour, so i need to extract the hour in the property createdAt in my order Model that's why i think to use The getHours() method returns the hour for the specified date.
My method:
#get('/orders/count-perHour/{date}', {
responses: {
'200': {
description: 'Order model count',
content: { 'application/json': { schema: CountSchema } },
},
},
})
async countPerHour(
#param.path.string('date') date: string): Promise<any> {
let dateStart=new Date(date) ;
dateStart.setSeconds(0);
dateStart.setMinutes(0);
let dateEnd=new Date(date) ;
dateEnd.setSeconds(59);
dateEnd.setMinutes(59);
return await this.orderRepository.count(
{
createdAt: {
lte: dateEnd
,
gte: dateStart
}
}
);
}
the property createdAt in mu model order:
#property({type: 'date',})createdAt?: string;
Error: it return false comparison, for example if i have one order at hour:10 it return 6

Mongoose aggregate throws error when zero match

I'm trying to aggregate a field in mongodb based on some conditions. This works fine when there's at least single match, but it throws error otherwise. I want to the aggregated value as 0 when there's no match. What need to be modified in below code?
Note: fromDate and toDate passed through API Payload.
"errorMesssage": "Cannot read property 'amount' of undefined"
DTO:
export class MetricsDTO {
fromDate: Date;
toDate: Date;
}
Service
async getRevenue(metricClause: Partial<MetricsDTO>) {
if (Object.keys(metricClause).length == 0) {
var rev = await this.bookingsModel.aggregate([
{ $group: { _id: null, amount: { $sum: '$GrossAmount' } } }
])
} else {
var rev = await this.bookingsModel.aggregate([
{ $match: { TxnDate: { $gte: metricClause.fromDate, $lte: metricClause.toDate } } },
{ $group: { _id: null, amount: { $sum: '$GrossAmount' } } }
])
}
return rev[0].amount;
}
Payload
{
"fromDate": "2019-10-24",
"toDate": "2019-10-25"
}
It is not the aggreage causing the exception, this line causes the error, because you didn't check if the rev is null or not.
return rev[0].amount;
So to handle null, you can:
if (rev && rev[0]) {
return rev[0].amount;
} else {
return 0;
}

get first and last values for each condition

I have a collection like this:
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-08T00:00:00Z'),
item_type: "book",
book_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdf23'),
rating: 6
},
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-05T00:00:00Z'),
item_type: "movie",
movie_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdfa'),
rating: 5
},
{
_id: ObjectId('534343df3232'),
date: ISODate('2016-01-010T00:00:00Z'),
item_type: "song",
song_id: ObjectId('534343df3232fdf'),
user_id: ObjectId('534343df3232fdf13'),
rating: 9
}
There can be only one rating per item per user per day.
I would like to check how the ratings evolve between a period of time for a selection of users and items. I need only the first and the last rating for each book/movie/song.
I have no idea on how I could do this the most efficient way.
As for now, I'm retrieving all the ratings for all the users, and then parsing them with PHP.
db.ratings.find({user_id:{$in:[...]}, $or:[book_id:{$in:[...]}, song_id:{$in:[...]}, movie_id:{$in:[...]}, ], date:{$gte:.., $lte..} });
This is obviously unefficient but I don't know how to handle this case.
You can do it with mongodb mapReduce. So at first you need to filter your data on date range, selection of users and selection of items(query part). Then group by item(map part) and for each item select first and last days with corresponding ratings(reduce part).
Try the following query:
var query = {
user_id: {$in:[...]}
date: { $gte: dateFrom, $lt:dateTo},
$or: [
{book_id: {$in:[...]}},
{song_id:{$in:[...]}},
{movie_id:{$in:[...]}}
]
}
var map = function () {
emit(this.item_type, {
first : {rating: this.rating, date: this.date},
last: {rating: this.rating, date: this.date}
})
}
var reduce = function (key, values) {
var res = values[0];
for (var i=1; i<values.length; i++ ) {
if (values[i].first.date < res.first.date)
res.first = values[i].first;
if (values[i].last.date > res.last.date)
res.last = values[i].last;
}
return res;
}
db.collection.mapReduce( map , reduce , { out: { inline : true }, query: query } )