I'm building a Rails application, which is creating orders from a schedule. The schedule has a time in format hh:mm, and ticks for each day of the week. A method occasionally checks the schedules, and creates any orders required by the schedule.
Firstly, I build up the time for this week's order in a Ruby DateTime object, then check if it exists, and create if not e.g.:
order = Order.where( :delivery_datetime = del_datetime )
unless order.any?
Order.create( :status => 'Estimated', :delivery_datetime => del_datetime )
end
That works as expected on my machine, but when other people picked it up from the repository, it would recreate the orders every time. I investigated the SQL it was using, and the problem seemed to be it was creating a where clause slightly different to the insert statement:
SELECT COUNT(*) FROM orders WHERE delivery_datetime = '2011-06-30 18:00:00.000000'
INSERT INTO orders (delivery_datetime) VALUES ('2011-06-30 18:00:00.000000000')
So the difference is the three extra zeroes in the partial second field. I understand SQLite doesn't have real date types, so these are different just because the strings are different. The problem I am having now is that I can't seem to force the format of the inserted string. E.g. even if I do the following:
Order.create( :status => 'Estimated',
:delivery_datetime => del_datetime.strftime( '%Y-%m-%d %H:%M' ) )
the insert statement still uses a 'standard' format - but with 6 zeroes on my instance, and 9 on another.
No answers! Not seen that on Stack Overflow before. Right now, I have 'fixed' it by declaring the database field as a string, and then using strftime to ensure the date format remains the same. This works, but doesn't seem ideal.
After reading all the stuff within Sqlite docs about datatypes being a 'misfeature', I'm thinking of dropping it for Postgres or similar. I want datetimes to be datetimes, and for it not to randomly decide that 18:00:00.000000 and 18:00:00.000000000 are different times...
Related
I have Events, with a date, and a name, that has_many Minutes
I need to find all the events that are the first one by date to have the given name, then collect all of the minutes associated with those events.
Event.order(date: :asc).uniq_by(&:name).map(&:minutes).flatten
almost works. except that I end up with an array, and not an ActiveRecord::Relation, and that I'd really like to start on Minute and make this into a scope like... Minute.first_time_events() gives me all the minutes from events that were the first event by date with their name.
One final caveat, I'm using rails 3.2. Thanks
If you don't mind having raw sql in your app you could do it all in one call with
Minute.where(event_id: Event.joins(
"INNER JOIN (SELECT url, MIN(date) AS minDate FROM events GROUP BY name) groupedEvents ON events.name = groupedEvents.name AND events.date = groupedEvents.minDate"
))
If you don't mind making two calls to the db then I would definitely go with the readability of Max's suggestion
ids = Event.order(date: :asc).uniq_by(&:name).map(&:id)
minutes = Minute.where(event_id: ids)
I would pull the ids of the events and then use that in a scope to fetch Minutes.
ids = Event.uniq(:name).order(date: :asc).ids
minutes = Minute.joins(:event).where(events: { id: ids })
The caveat here is that this is how I would do it in Rails 4. Some backporting may be necessary but you should really be focusing on updating your app since 3.2 will be EOL any day now.
In my Expense model I have a date attribute called payment_date. This is a Date format and not DateTime.
In one of my views Im displaying this data in a few different formats. and I want to avoid multiple queries.
For example, right next to Expense.all I need to display expenses year to date. Rather than running two queries to pull essentially the same information, I thought I would try to pluck the YTD data from #expenses = Expense.all.
Right now I'm trying to use:
#expenses.select { |ex| ex.payment_date > Date.today.beginning_of_year }
but this is returning a blank array.
Is it possible to select results by date, and where am i messing up?
To include Jan 1 of this year in your YTD expenses, use >= instead of > in your select block.
Since you tagged this with Rails, an even more performant way to query this is by using ActiveRecord/SQL.
If you have many records, doing #expenses = Expense.all and then using the Ruby enumerable select on that collection will load all of the expenses from the DB into memory. This could be quite slow, or could even cause out-of-memory errors!
You can do (assuming the DB is Postgres):
#ytd_expenses = Expense.where("payment_date >= ?", Date.today.beginning_of_year)
This will only return the results you care about from the DB.
I have to update an age column based on the value in a date of birth column. There are thousands of records to update.
How do I do this using rails?
Is this the right way to do it?
User.update_all(:age => some_method);
def some_method
age = Date.today.year - dob.year
end
Yes, update_all is the right method but no, you can't do it like this. Your some_method will only get called once to set up a database call (I assume you're persisting to a database). You'll then get an error because dob won't be recognised in the scope of the User class.
You'll need to translate your date logic to SQL functions.
Something like (for mysql):
User.update_all("age = year(now()) -
year(dob) -
(DATE_FORMAT(now(), '%m%d') < DATE_FORMAT(dob, '%m%d'))")
(NB. the date_format stuff is so that you get the right age for people who's birthdays are later in the year than the current date - see this question for more details)
The other option is to use one of the batches functionality in rails.
User.where(some_condition).find_in_batches do |group_of_users|
# some logic
# e.g. group_of_users.update_all(:age => some_logic)
end
This would lock your db for less time. Note that you should pretty much always update with a condition in mind. I can't think of many cases you would want to update an entire table every time something happens.
There are a few options checkout the rails docs or the api.
your query is right.
There are many way to update record in a batch/lot.
But, I think that your query is best. Because it is rails query that will support every condition for all database.
for updating more than one attributes
Model.update_all(:column1 => value1, :column2 => value2, ........)
or
you can use :
Model.update_all("column1 = value1, column2 = value2, ........")
I'm calculating total "walk time" for dog walking app. The Walks table has two cols, start_time and end_time. Since I want to display total time out for ALL walks for a particular dog, I should just be able to sum the two columns, subtract end_times_total from start_time_totals and result will be my total time out. However I'm getting strange results. When I sum the columns thusly,
start_times = dog.walks.sum('start_time')
end_times = dog.walks.sum('end_time')
BOTH start_times and end_times return the same value. Doing a sanity check I see that my start and end times in the db are indeed set as I would expect them to be (start times in the morning, end times in the afternoon), so the sum should definitely return a different value for each of the columns. Additionally, the value is different for each dog and in line with the relative values I would expect, so dogs with more walks return larger values than dogs with fewer walks. So, it looks like the sum is probably working, only somehow returning the same value for each column.
Btw, running this in dev Rails 3.2.3, ruby 2.0, SQLite.
Don't think that summing datetimes is a good idea. What you need is calculate duration of each single walk and sum them. You can do it in 2 ways:
1. DB-dependent, but more efficient:
# sqlite in dev and test modes
sql = "strftime('%s',end_time) - strftime('%s',start_time)" if !Rails.env.production?
# production with postgres
sql = "extract(epoch from end_time - start_time)" if Rails.env.production?
total = dog.walks.sum(sql)
2. DB-agnostic, but less efficient in case of hundreds record for each dog:
total = dog.walks.all.inject(0) {|tot,w| tot+=w.end_time-w.start_time}
I don't know, how sqlite handles datetime and operations on this data type, but while playing in sqlite console, I noticed that I could get reliable effects when converting datetime to seconds.
I would write it like:
dog.walks.sum("strftime('%s', end_time) - strftime('%s', start_time)")
Query should look like:
select sum(strftime('%s', end_time) - strftime('%s', start_time)) from walks;
I have a DB Table for a Model entitled TradeDailyAverage. It has a date (DateTime) & averageprice (decimal) column
When I run this, I can't get the averageprice attribute to update:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage
Further, when I run this, the date will show up, but the averageprice will not show up in rails console. It only shows up as blank:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
puts newaverage.averageprice
puts newaverage.date
Is there anything special that I need to do to averageprice before I save it?
Here is all of the entire Rake Task for your reference:
averages = Trade.where('date >= ?', 7.days.ago).average(:price, :group => "DATE_TRUNC('day', date - INTERVAL '1 hour')")
# Loops through each daily average produced above
averages.each do |date, avg|
# Converts the BigDecimal to Floating Point(?)
averagefloat = avg.to_f
# Rounds the Daily Average to only two decimal points
dailyaverage = number_with_precision(averagefloat, :precision => 2)
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage
If you want to use find_or_initialize_by you need to think carefully about the implications. Lets take your first example:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.update_attributes :averageprice => dailyaverage
This should work, when the TradeDailyAverage for the given date is already in the database. It should not work however, when you get a new record back. The reason is simply because a new record is not persisted to the database. There is no way for update_attributes to update a non persisted record. You have two options here:
1.) Do not use update_attributes but assign the value and call save. This works for both, new and created records:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
newaverage.averageprice = dailyaverage
newaverage.save
2.) Do not use find_or_initialize_by but find_or_create_by. This way if the record does not exist, a new one is directly written to the database. Now update_attributes should work because you always get persisted records back. Note that this approach has the drawback that you save records without an averageprice to the database. I would not recommend that.
The explanation above should also explain your second example:
newaverage = TradeDailyAverage.find_or_initialize_by_date(date)
puts newaverage.averageprice
puts newaverage.date
This should output the averageprice and the date for persisted records. If you get a newly initialized record back though, it will only display the date. Since you only initialized the record with a date object, there is no way that the averageprice is already set.
My issue was simply that upon saving to my database, PostgreSQL was changing the hourly time, possibly due to a timezone issue. Thus, all of my instances above were new, and I couldn't update attributes of an existing model. So, I converted my datetime data to dates, changed my date db column to date instead of datetime, and now everything is working smoothly. Yves gives some great info above though which helped me later on.