rails order by created_at attribute of nested jsonb data - ruby-on-rails

I have a bunch of customers that need to be sorted by the time they were "completed" according to our system. I can do it like this and it works ok:
customers = Customer.where(state: 'review').joins(:audits)
customers.sort_by { |c| c.onboarding_finished_at.to_i }
or...it did until it got onto our staging environment where we have enough customers with enough audits that it caused some major performance issues. Now i'm searching for a way to sort customers by the created_at attribute of the last state transition, but my query-foo is woefully inadequate.
this is a simplified version of the audits data associated with a customer. Note the audited_changes column which is jsonb:
irb(main):032:0> customers.last.audits.last
=> #<Audited::Audit id: 642691, auditable_id: 45517, action: "update", audited_changes: {"state"=>[0, 1]}, created_at: "2020-08-13 08:59:00">
I can access the audits but i can't for the life of me figure out how to get the last transition to state "1" (cause there could be multiple) and then pluck the created_at of that and then sort customers by that created_at value.
Any push in the right direction is very much appreciated!

You can do an inner joins on the table and run a query to check second element of state array (assuming that's the structure for all the records) is 1.
Customer.includes(:audits).where("audited_changes->'$.state[1]' = 1").reorder("audits.created_at asc")
Let me know if that's what you're looking for

Related

Getting details of other columns from active record joins

I have following join where I am getting Review tables all columns but I need to add other columns but it is showing nothing for other column.
Review.joins(division: [{company: :region}, :departments, :employees]).select('reviews.id, companies.name as company, region.name, divisions.name as division).limit(10)
But I am getting only Reivews
[#<Review:0x00007ff5006bb910 id: 1>,
...
How can I get division.id, company.name and region.name
What you are seeing (only Review objects) is what you're supposed to see, Rails performs the join between Review, Devision, Company etc etc and brings you back the Review records resulting from that join. However the selected aliased columns should be available as methods on the Review records:
reviews = Review.joins(...)
reviews.first.company
reviews.first.division
Note that inspect method doesn't show non column attributes so your console might hide the aliased columns (company, division) but they are there.
Since it seems like you're not after only filtering but you need association data, you might be better off exchanging this with eager_loading since if you try to access the associations like that you will fire n+1 queries.
Review.includes(division: [{company: :region}, :departments, :employees]).select('reviews.id, companies.name as company, region.name, divisions.name as division).limit(10)

How to get the latest created object in ruby on rails [duplicate]

I was wondering if there is a way to find the newest record in a table in rails3?
Given a Post model, you could do #post = Post.order("created_at").last
(The reason I didn't just do a #post = Post.last is because that always defaults to sort by your primary key (usually id). Most of the time this is fine, but I'm sure there's a scenario where that could cause problems (e.g. setting custom IDs on records, database changes which affect the primary key sequencing/autonumbering, etc.). Sorting by the created_at timestamp ensures you are really getting the most recent record).
While dmarkow's answer is technically correct, you'll need to make an index on created_at or risk an increasingly slow query as your database grows.
If you know that your "id" column is an auto-increment primary key (which it likely is), then just use it since it is an index by definition.
Also, unless AREL is optimized to select only one record in a find(:last), you run the risk of making it select ALL records, then return you just the last one by using the "last()" method. More efficient is to limit the results to one:
MyModel.last(:order => "id asc", :limit => 1)
or
MyModel.first(:order => "id desc", :limit => 1)
you may run into ambiguity issues using created_at on a sufficiently high-traffic table.
eg. try:
INSERT INTO table (created_at) VALUES ( NOW() );
INSERT INTO table (created_at) VALUES ( NOW() );
..has the potential to have the same created_at, which only has 1 second of resolution. a sort would return them in no particular order.
you may be better off storing a microtime value and sorting on that.
Yes, you can use the method .last
So if your model is called Post then:
>> Post.last
=> #<Post ...>
Try, for a model named ModelName:
record = ModelName.last

Need help writing sql query in rails so I get ActiveRelation

I need an active record relation that gives me the latest record of a region, city, bed combination. I have the sql query written as below, but I need to figure out if there is away to use a different approach to have it return an active record relation and not an array. Any suggestions?
Current query:
#current_ltm_market_stats = LtmStatsByBedCount.find_by_sql(" SELECT *
FROM ltm_stats_by_bed_counts lstats
WHERE lstats.city_id = '#{#city_id}'
#{#region_id_condition}
AND (lstats.beds,lstats.city_id,lstats.region_id,lstats.reporting_date)
IN (SELECT lstats.beds,
lstats.city_id,
lstats.region_id,
max(lstats.reporting_date)
FROM ltm_stats_by_bed_counts lstats
WHERE lstats.city_id = '#{#city_id}'
#{#region_id_condition}
GROUP BY city_id, region_id, beds)
ORDER BY lstats.year DESC,lstats.month DESC")
I had tried this before which did result in a relation but it runs really slowly and the result is not exactly the same. Are there any better rails ways to do this?
#all_ltm_market_stats = LtmStatsByBedCount.where(city_id: #market.city_id, region_id: #market.region_id)
#current_ltm_market_stats = #latest_year_ltm_market_stats.where(month: #latest_year_ltm_market_stats.all_ltm_market_stats.select('Max(year)'))
Information in the question is incomplete, so i might have to update my answer when additional details are added, But here is the initial draft with available information:
#current_ltm_market_stats = LtmStatsByBedCount.
where(city_id: #city_id).
where(#region_id_condition).
where("(beds, city_id, region_id, reporting_date) IN (
SELECT lstats.beds,
lstats.city_id,
lstats.region_id,
max(lstats.reporting_date)
FROM ltm_stats_by_bed_counts lstats
WHERE lstats.city_id = '#{#city_id}'
#{#region_id_condition}
GROUP BY city_id, region_id, beds)").
order(year: :desc, month: :desc)
Note that you might have to adjust your #region_id_condition a bit for this to work.
Theoretically it is equivalent of your SQL version(which means it will generate same sql excluding table alias) and returns the AR relation object. Which is the only requirement in the question. Obviously SQL might be improved with additional information as well.
Additionally, you will want to have carefully crafted indexes on this table if you are going to use this query on larger datasets frequently.

PostgreSQL in Rails: sorting object by two date attributes in descending order

I have an index of active job positions. Currently, they're sorted by the most recent i.e. created_at. However, recently i've added in a renewal feature that updates a renewal_date attribute without updating the created_at.
What I want to achieve is to sort the list in descending order using both renewal_date and created_at.
jobs = Job.where(active: true).reorder("renewal_date DESC NULLS LAST", "created_at DESC")
With this code, the renewed job will always be at the top regardless of how many new jobs are created. How do I sort it so it checks for the date for both attributes and sorts it according to most recent?
Your code will order first by renewal_date with nulls at the end, and then will look at the created_at if two records have the same renewal_date.
I assume that what you want to do is something like "max(renewal_date, created_at)", which will take the "last modification date", or another custom way to compare the two fields.
If then, you can find your answer here : merge and order two columns in the same model
Job.where(active: true).reorder('GREATEST(renewal_date, created_at) DESC')
Let try a standard SQL, so it can work with all types of database:
Job.where(active: true).order('CASE WHEN renewal_date IS NULL THEN created_at ELSE renewal_date END DESC')

Find the newest record in Rails 3

I was wondering if there is a way to find the newest record in a table in rails3?
Given a Post model, you could do #post = Post.order("created_at").last
(The reason I didn't just do a #post = Post.last is because that always defaults to sort by your primary key (usually id). Most of the time this is fine, but I'm sure there's a scenario where that could cause problems (e.g. setting custom IDs on records, database changes which affect the primary key sequencing/autonumbering, etc.). Sorting by the created_at timestamp ensures you are really getting the most recent record).
While dmarkow's answer is technically correct, you'll need to make an index on created_at or risk an increasingly slow query as your database grows.
If you know that your "id" column is an auto-increment primary key (which it likely is), then just use it since it is an index by definition.
Also, unless AREL is optimized to select only one record in a find(:last), you run the risk of making it select ALL records, then return you just the last one by using the "last()" method. More efficient is to limit the results to one:
MyModel.last(:order => "id asc", :limit => 1)
or
MyModel.first(:order => "id desc", :limit => 1)
you may run into ambiguity issues using created_at on a sufficiently high-traffic table.
eg. try:
INSERT INTO table (created_at) VALUES ( NOW() );
INSERT INTO table (created_at) VALUES ( NOW() );
..has the potential to have the same created_at, which only has 1 second of resolution. a sort would return them in no particular order.
you may be better off storing a microtime value and sorting on that.
Yes, you can use the method .last
So if your model is called Post then:
>> Post.last
=> #<Post ...>
Try, for a model named ModelName:
record = ModelName.last

Resources