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.
Related
I have an ActiveRecord request:
Post.all.select { |p| Date.today < p.created_at.weeks_since(2) }
And I want to be able to see what SQL request this produces using .to_sql
The error I get is: NoMethodError: undefined method 'to_sql'
TIA!
ISSUE
There are 2 types of select when it comes to ActiveRecord objects, from the Docs
select with a Block.
First: takes a block so it can be used just like Array#select.
This will build an array of objects from the database for the scope, converting them into an array and iterating through them using Array#select.
This is what you are using right now. This implementation will load every post instantiate a Post object and then iterating over each Post using Array#select to filter the results into an Array. This is highly inefficient, cannot be chained with other AR semantics (e.g. where,order,etc.) and will cause very long lags at scale. (This is also what is causing your error because Array does not have a to_sql method)
select with a list of columns (or a String if you prefer)
Second: Modifies the SELECT statement for the query so that only certain fields are retrieved...
This version is unnecessary in your case as you do not wish to limit the columns returned by the query to posts.
Suggested Resolution:
Instead what you are looking for is a WHERE clause to filter the records at the database level before returning them to the ORM.
Your current filter is (X < Y + 2)
Date.today < p.created_at.weeks_since(2)
which means Today's Date is less than Created At plus 2 Weeks.
We can invert this criteria to make it easier to query by switching this to Today's Date minus 2 weeks is less than Created At. (X - 2 < Y)
Date.today.weeks_ago(2) < p.created_at
This is equivalent to p.created_at > Date.today.weeks_ago(2) which we can convert to a where clause using standard ActiveRecord query methods:
Post.where(created_at: Date.today.weeks_ago(2)...)
This will result in SQL like:
SELECT
posts.*
FROM
posts.*
WHERE
posts.created_at > '2022-10-28'
Notes:
created_at is a TimeStamp so it might be better to use Time.now vs Date.today.
Additional concerns may be involved from a time zone perspective since you will be performing date/time specific comparisons.
You need to call to_sql on a relation. select executes the query and gives you the result, and on the result you don't have to_sql method.
There are similar questions which you can look at as they offer some alternatives.
Relatively new to SQL and ORM.
Let's say I have a database table with start_at and finish_at fields (both datetime). Table contains 10000 items for example.
How to calculate the average, max/min duration (finish_at - start_at) using ruby or Active Record tools? There is no need to write it somewhere, just need numbers.
table = MyClass.arel_table
duration = table[:finish_at] - table[:start_at]
MyClass.pick(duration.maximum, duration.minimum, duration.average)
#=> ["125 days 20:46:34.05816", "00:00:00.063579", "20 days 23:30:16.221092"]
Cast them to another data type as needed or use directly in other queries.
Given a table concerts and a column concerts.occurence_dates of Array type.
I need to select concerts which occurs within from_date and to_date using ActiveRecord.
I just figured out how to select events after from_date:
Concert.where(':from_date < ANY(occurrence_dates)', from_date: from_date)
As mentioned in the official documentation
Arrays are not sets; searching for specific array elements can be a sign of database misdesign. Consider using a separate table with a row for each item that would be an array element. This will be easier to search, and is likely to scale better for a large number of elements.
This also applies to your case, trying to perform a search using specific array elements.
You are using the wrong field. What you really need to use are two separate columns, one for the from_date and the other for the to_date, along with two indexes.
The indexes will ensure a fast lookup, the separate columns will allow you to search passing a range of dates or simply using
Concert.where('from_date >= ? AND to_date <= ?', from_date, to_date)
With the current setup, your best solution is to scan all the records, select each occurrence_dates and check whether they match your criteria.
Possible solution:
Concert.where('(occurrence_dates[0], occurrence_dates[array_length(occurrence_dates, 1)]) OVERLAPS (:from_date, :to_date)', from_date: from_date, to_date: to_date)
Sometimes, I need to query lots of data in DB for data processing.
For example, I have a table: Activity, I want to find all the users which create activity in the last month,
I am only concerned about the data arrays, and I don't want to create a lot of Activity models,
Is there any way I can do it?
like this code:
Activity.where('created_at > ?', Time.now - 1.month).get_data(:user_id, :created_at)
=> [[1, 2012-02-01] .... ]
Try this approach:
sql = "SELECT * from activities limit 10"
result = ActiveRecord::Base.connection.execute(sql)
result.to_a
I'm not sure about sql query but I think you get the idea.
Try:
Activity.where(['activities.user_id = ?
AND activities.created_at BETWEEN ?
AND ?', users, 1.month.ago, Time.zone.now])
This will return all of the activities that have been created over the past month for a set of users. The only potential issue with this query is with extremely large datasets. You might want to use a named_scope to narrow down the activities before you run this query so that it doesn't search over every activity in the db.
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...