Reputation: 14645
I'm trying to improve the SQL for the following schedule:
It looks something like this:
- @users.each do |user|
- @dates.each do |date|
%td
- Booking.where(user: user, date: date).each do |booking|
= booking.shift_time
@users
consists of users present in the table.
@dates
consists of dates: beginning of week -> end of week
Obviously this code suck because a query is fired for each date, booking and user. Having more than a few users would take a long time for the page to load.
How can I improve this query? Is it even possible to get all this data out of the database just by using a one (big) query? I'm using PostgreSQL if that makes any difference.
One suggestion is that I get all bookings that are between @dates
and group by dates. But this means that I will have to replace non-existent bookings for particular date and user with a NULL
value but I have no idea how I would be able to do that.
Here is the sql output:
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-09'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-10'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-11'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-12'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-13'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-14'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 1 AND "bookings"."date" = '2015-03-15'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-09'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-10'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-11'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-12'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-13'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-14'
SELECT "bookings".* FROM "bookings" WHERE "bookings"."user_id" = 2 AND "bookings"."date" = '2015-03-15'
Upvotes: 1
Views: 104
Reputation: 42869
# action
@dates = start_date..end_date
@users = User.some_scope.includes(:bookings)
# view
- @users.each do |user|
%tr
- @dates.each do |date|
%td
= user.bookings.select{|booking| booking.date == date}.each do |user_booking|
= user_booking
This should only hit the database twice ( 2 queries one for users and one for bookings )
Upvotes: 0
Reputation: 54882
You can do as following:
# controller
def your_action
@users = User.some_scope
@dates = some_logic_to_return_array_of_dates
bookings = Booking.where(user_id: @users.map(&:id), date: @dates)
# the above will select all Booking record having user_id in the selected users' ids AND date IN the selected range/array of dates
@bookings_by_date = bookings.group_by(&:date)
# group results like
# {
# <Date> => [<Booking>, <Booking>],
# <Date2> => [<Booking>]
# }
end
Then in your view:
# view
- @users.each do |user|
- @dates.each do |date|
%td
- todays_bookings = @bookings_by_date[date].presence || []
- todays_bookings.select{ |booking| booking.user == user }.each do |booking|
= booking.shift_time
Upvotes: 1