Closest day in array sql - sql

I'm trying to find a record and order it by the closets day based on the current day.
Let me try to illustrate with an example.
Say that John wants to find out when he has to teach next. John teaches the following days (days are converted to numbers, where 0 = Sunday, 1 = Monday ...) [1,2]. The current day is Friday (5) and so the result should be 1.
Another example:
Karen wants to figure out when she has to teach next. Karen teaches the following days [0,2,3]. The current day is Thursday (4) and so the result should be 0.
Current query:
TeamOverview.where(coach: current_user.id).order(:day = [next closets day missing here])
Model:
t.string :name
t.integer :coach
t.int :day
Possible records in model:
id: 1, name: John, day: 1
id: 2, name: John, day: 2
id: 3, name: Karen, day: 0
id: 4, name: Karen, day: 2
id: 5, name: Karen, day: 3

To avoid a complex solution I'd rather select all working days and do the work in ruby. Max number of results from query is 7 - that should be fine. Additionally changes are high that you already have such a query (maybe scoped on TeamOverview), you can reuse this and on a second call it may also come from query cache.
working_days = TeamOverview.where(coach: current_user.id).pluck(:day)
working_days.find { |day_num| day_num > today_num } || working_days.first

For getting the next day sorted top you will need to order by distance from current day.
Unfortunately, the %(modulo operator with postgresql keeps the sign
for negative values. Thus, you can not use day - current_day.
But the ordering on following expression will sort the rows according to your intened logic:
(7 + day - current_day) % 7
WHere day is the day form the model row and current_dayis the value for the current day to use with the calculation.
If you prefer a more explicit solution you might turn to
CASE WHEN day < current_day THEN 7 + day - current_day ELSE day - current_day END

(res =TeamOverview.where(coach: current_user.id).order("day").find(:first, :conditions => [ "(day > ? )",current_day]) ) ? res : TeamOverview.where(coach: current_user.id).order("day").find(:first, :conditions => [ "(day < ? )",current_day])
This will give you desired result

You can achieve this in pure SQL with a two-part ordering:
SELECT *
FROM team_overviews
ORDER BY day < (current_day), day
A simple implementation in ActiveRecord for this is:
TeamOverview
.where(coach: current_user.id)
.order("day < #{current_day}")
.order(:day)
Or, if you prefer:
TeamOverview
.where(coach: current_user.id)
.order("day < #{current_day}, day")
Warning: The above code is potentially vulnerable to SQL injection. Be sure that you trust the source of current_day, and sanitise (in this case, probably just call to_i) if necessary.
Unfortunately, looking at the rails source code (as of the current 4.2.6 version at time of posting), it looks as though the ? syntax is not supported by the order ActiveRecord method - i.e. the following is invalid: TeamOverview.order("day < ?", current_day). Perhaps that would be a good addition to the library.

Following code will find next upcoming day, if you need days then remove .first argument
next_day = TeamOverview.where("day > ? and coach_id = ?", current_day, current_user.id).order("day").first

Related

Eloquent / DB get months between two dates

I'm using Laravel 5.4.
I have a Booking model which contains a start_date and an end_date. The user chooses a month from a dropdown list.
I would like Eloquent to return all records where the month is between the boundaries of the start_date and end_date.
e.g Show all Bookings that are in September (even if the booking started before (or during September) AND (boolean) ends in September or later.
Any suggestion would be welcome. Happy for this to be a raw SQL statement if needs be.
This is what worked for me:
$start = Carbon::now()->addMonth(-1);
$end = Carbon::now()->addMonth();
$bookings = Booking::whereRaw('MONTH(created_at) > :0 AND MONTH(created_at) < :1',[
$start->month, $end->month
])
->orWhereRaw('MONTH(created_at) = :3 AND DAYOFMONTH(created_at) >= :4', [
$start->month, $start->day
])
->orWhereRaw('MONTH(created_at) = :5 AND DAYOFMONTH(created_at) <= :6', [
$end->month, $end->day
])
->get();
If you want to exclude the start and end dates you can replace >= with > and <= with <.
And ofcourse, you have to change the $start and $end dates. They should be Carbon\Carbon objects.
First, on client side or server side parse the users picked month to something like:
$selected = "2017-09";
Then:
Booking::where([
['start_time','>', $selected],
['end_time','<', $selected]
])->get()
This seems to work:
$bookings = DB::table('bookings')
->whereMonth('start_date', '<', $date->month)->whereMonth('end_date', '>=', $date->month)
->orWhere(function($query) use ($date) {
$query->whereMonth('start_date', $date->month);
})->get();
Should be pretty straightforward.
I'm not sure what format the "month" is in but you should use DateTime::createFromFormat() to get your DateTime object. Feel free to use Carbon if you are familiar. I find for simple things like this, it's adding unnecessary complexity.
Then query using it.
Booking::whereBetween($dateTime->format('Y-m-00'), [DB::raw('start_date'), DB::raw('end_date')])->get();
Adding some explain :
Booking::whereRaw("(start_date, end_date) overlaps (date '2021-06-01', date '2021-06-30')")->get();

Progress date comparision

I am trying to make a query in Progress. I should select all records older than exactly one year, so the current date minus 1 year. I have tried several possibilities but became every time an error. The query belongs to a join and should take every record of the previous year up to the current date minus one year:
left outer join data.pub."vc-669" as det2
on deb.cddeb = det2.cddeb
and det2.jaar = year(curdate()) - 1
and det2."sys-date" < date(month(curdate()), day(curdate()), year(curdate()) - 1)
That should simply be:
and det2."sys-date" < add-interval( curdate(), - 1, 'year' )
(As this already deals with the year, there is no need to look at det2.jaar, too.)
https://documentation.progress.com/output/ua/OpenEdge_latest/index.html#page/dvref/add-interval-function.html

How to Get the total number of (the differnece between two dates)?

I i have a table [holidays] with the following structure :
id | start_date | end_date | user_id
How to get the number of holidays for a user in the previous year ?
I wanna something like that with correct syntax:
SELECT SUM(end_date - start_date)
FROM holidays
WHERE user_id = 342
AND YEAR(end_date) = YEAR(CURRENT) - 1
I think this should work in informix:
SELECT SUM(end_date-start_date)
from holidays
where user_id = 342 and
YEAR(end_date) = YEAR(TODAY)-1;
Note: it is not clear whether the end date is inclusive or not. You might want:
SELECT SUM((end_date-start_date) + 1)
from holidays
where user_id = 342 and
YEAR(end_date) = YEAR(TODAY)-1;
Your query is perfectly fine. You don't have any syntax improvements to be done. The only thing Gordon Linoff's approach (which will work) differs from yours is that he is using TODAY (that is YEAR TO DAY) instead of CURRENT (that is YEAR TO FRACTION), but YEAR(CURRENT) and YEAR(TODAY) would get the exact same result.
The only way you would get a different result was if you had CURRENT inside the SUM, like SUM(CURRENT-start_date). That way you would have more precision than only the days (You would get the days and then hh:mm:ss.fff) like you want, but if you used TODAY, you would get only the days.
Other than that, it's perfectly fine.

Count of records that are created the same day

I am trying to create a array that should be something like this:
[ [created_at date * 1000, count record for that date],
[created_at date * 1000, count record for that date],
[created_at date * 1000, count record for that date] ]
The created_at date is not exactly the same, because of minutes, hours and seconds.
I was thinking is it possible to change created_at time on create to 00:00:00
I have tried with this,
#kliks = Klik.all.map{|klik| [(klik.created_at.to_i * 1000), 1]}
But I have not figure out to sum those records that are created the same day. Also this loops create a array for every single record, I don't want duplicates of the sum.
Rails has ActiveRecord::Calculations which is designed to do exactly this sort of thing at the database level. You should use it. In this case, count is the method you want:
#kliks = Klik.count( :group => "DATE( created_at )" )
This is equivalent to the following SQL:
SELECT *, COUNT(*) FROM kliks
GROUP BY DATE( created_at )
The DATE() function is MySQL changes a datetime (like created_at, e.g. 2012-02-27 10:08:59) to a plain date (e.g. 2012-02-27). No need to go converting things to integers or multiplying minutes and seconds, and no need to use map or any other method in Ruby.
According to the query guide, you should try with
items = Klik.select("date(created_at) as creation_date, count(*) as count").group("date(creation_date)")
result = items.map { |k| [ k['creation_date'], k['count'] ] }
The following will produce the result you have asked for:
Klik.all.group_by do |k|
k.created_at.beginning_of_day
end.map do |date, records|
[date, records.length]
end

Sql where t.date attribute is less than current year

In my Rails 3 app, I'm attempting to do a find of current students by their school's name and their graduation date in relation to the current year. I can do a successful find for users without a graduation date (see below), but I want to search users who have a graduation date attribute less than - or greater than - the current year. FYI, I'm using PostgreSQL.
The fields I'm using are set up as follows:
t.string :high_school
t.date :hs_grad_year
Here's the find I have working currently:
<%= pluralize(Profile.where(:high_school => "#{#highschool.name}").where("hs_grad_year IS NOT NULL").count, "person") %>
There's a couple issues with your code (getting records from your database should be done in the controller, not in the view, and you're using high school names as foreign keys instead of an id field, for example), but to answer your question:
Profile.where %'
high_school = ? AND
EXTRACT(year FROM hs_grad_year) < EXTRACT(year FROM current_date)
',
#highschool.name
You want a .where("hs_grad_year < extract(year from current_date)") in there.
In Postgres, current_date is the current date (yyyy-mm-dd), and the extract function pulls just one part of that date out of it. You can read more about date and time functions in Postgres here.
Profile.
where(:high_school => #highschool.name).
where("hs_grad_year < ? OR hs_grad_year > ?", Date.today.year, Date.today.year)