ActiveRecord - Query with Condition of Last Element in Association - sql

Trying to do a sort of complex query in ActiveRecord right now. Not sure if it's possible to do entirely in ActiveRecord (i.e. without just filtering intermediate results in Rails). I'd like to do as much manipulation as possible in the database.
I have a PullRequest and Release model with a many-many relationship. There exist fields on the Release model is_rollback(boolean) and ended_at (datetime). I am trying to find all pull_requests for which their releases association 1) is nonempty and 2) ends with a release that has is_rollback = true when the releases are ordered by ended_at.
Here's what I have so far:
PullRequest
.joins(:releases) # filter pull requests with empty release associations
.having('releases.is_rollback = true AND releases.ended_at = MAX(releases.ended_at)') # where latest release is a rollback
.group('pull_requests.id, releases.ended_at, releases.is_rollback') # necessary with 'having'
The having clause is not working as expected though and the query is still returning pull requests whose latest release is not a rollback. Not hugely experienced with complex SQL queries so any help is appreciated. I'm using Rails 4, PostgreSQL db.
Thanks!

I was able to figure it out - here is the working query:
pull_requests
.joins(:releases).distinct
.group('releases.ended_at, pull_requests.id, releases.id')
.having('releases.ended_at IN (SELECT MAX(releases.ended_at))')
.where('releases.is_rollback = true')
Thanks.

Related

Continued issue using OrderBy and Take() with Included relations

I'm aware of numerous issues raised on the EF7 Github repo, and these still appear to be present with the latest RC2 nightly builds.
The issue appears to be when using OrderBy and Take() when you're trying to Include relations within your query. I believe, according to previous issues raised on Github that the SQL generated within the join is incorrect and does not take into account the OrderBy. After reading replies, people have suggested using Skip(0).Take(x) as a workaround, but unfortunately in my scenario, this didn't work.
In my specific scenario, I'm querying a model based on an ancestor PK. So, in order to work around this, instead of passing the PK straight into the query, I've added the ancestor model to a List<T> and used the following code in the query instead:
Before (not working when Including nested relations): p => p.examplePK == id
After (appears to be working):
p => myList.Select(c => c.myId).Contains(p.examplePK)
I'm not entirely sure why the 2nd example works and I would be grateful of any info that can be given - could there potentially be some client side evaluation going on here instead? From what I gather, the 2nd example will be performing a SQL IN statement, will it not?
Thanks in advance!

Complex SQL Query in Rails 4

I have a complicated query I need for a scope in my Rails app and have tried a lot of things with no luck. I've resorted to raw SQL via find_by_sql but wondering if any gurus wanted to take a shot. I will simplify the verbiage a bit for clarity, but the problem should be stated accurately.
I have Users. Users own many Records. One of them is marked current (#is_current = true) and the rest are not. Each CoiRecord has many Relationships. Relationships have a value for when they were active (active_when) which takes four values, [1..4].
Values 1 and 2 are considered recent. Values 3 and 4 are not.
The problem was ultimately to have a scopes (has_recent_relationships and has_no_recent_relationships) on User that filters on whether or not they have recent Relationships on current Record. (Old Records are irrelevant for this.) I tried create a recent and not_recent scope on Relationship, and then building the scopes on Record, combining with checking for is_current == 1. Here is where I failed. I have to move on with the app but have no choice but to use raw SQL and continue the app, hoping to revisit this later. I put that on User, the only context I really need it, and set aside the code for the scopes on the other objects.
The SQL that works, that correctly finds the Users who have recent relationships is below. The other just uses "= 0" instead "> 0" in the HAVING clause.
SELECT * FROM users WHERE `users`.`id` IN (
SELECT
records.owner_id
FROM `coi_records`
LEFT OUTER JOIN `relationships` ON `relationships`.`record_id` = `records`.`id`
WHERE `records`.`is_current` = 1
HAVING (
SELECT count(*)
FROM relationships
WHERE ((record_id = records.id) AND ((active_when = 1) OR (active_when = 2)))
) > 0
)
My instincts tell me this is complicated enough that my modeling probably could be redesigned and simplified, but the individual objects are pretty simple, just getting at this specific data from two objects away has become complicated.
Anyway, I'd appreciate any thoughts. I'm not expecting a full solution because, ick. Just thought the masochists among you might find this amusing.
Have you tried using Arel directly and this website?
Just copy-and-pasting your query you get this:
User.select(Arel.star).where(
User.arel_table[:id].in(
Relationship.select(Arel.star.count).where(
Arel::Nodes::Group.new(
Relationship.arel_table[:record_id].eq(Record.arel_table[:id]).and(
Relationship.arel_table[:active_when].eq(1).or(Relationship.arel_table[:active_when].eq(2))
)
)
).joins(
CoiRecord.arel_table.join(Relationship.arel_table, Arel::Nodes::OuterJoin).on(
Relationship.arel_table[:record_id].eq(Record.arel_table[:id])
).join_sources
).ast
)
)
I managed to find a way to create what I needed which returns ActiveRelationship objects, which simplifies a lot of other code. Here's what I came up with. This might not scale well, but this app will probably not end up with so much data that it will be a problem.
I created two scope methods. The second depends on the first to simplify things:
def self.has_recent_relationships
joins(records_owned: :relationships)
.merge(Record.current)
.where("(active_when = 1) OR (active_when = 2)")
.distinct
end
def self.has_no_recent_relationships
users_with_recent_relationships = User.has_recent_relationships.pluck(:id)
if users_with_recent_relationships.length == 0
User.all
else
User.where("id not in (?)", users_with_recent_relationships.to_a)
end
end
The first finds Users with recent relationships by just joining Record, merging with a scope that selects current records (should be only one), and looks for the correct active_when values. Easy enough.
The second method finds Users who DO have recent relationships (using the first method.) If there are none, then all Users are in the set of those with no recent relationships, and I return User.all (this will really never happen in the wild, but in theory it could.) Otherwise I return the inverse of those who do have recent relationships, using the SQL keywords NOT IN and an array. It's this part that could be non-performant if the array gets large, but I'm going with it for the moment.

find_by_sql renders an array

I got some problems here, I can't make my find_by_sql request to render an ActiveRecord relation. Indeed, I need an activerecord relation to make a new request:
#searches = #searches.find_by_sql('SELECT *, COUNT( follower_id ) FROM follows GROUP BY followable_id LIMIT 0 , 3') if params[:only_famous_projects]
#project_pages = #project_pages.where(:project_id => #searches.pluck(:'followable.id')) if params[:only_famous_projects]
I can't use "pluck" without an activerecord relation. Therefore, I think I have to convert my sql request to an Activerecord request. However, as soon as I use "count" on ActiveRecord, I have an huge problem: I don't have on the end an ActiveRecord relation, but a FixNum!
I don't know where to find the answer anymore, I would be really gratefull if you could help me.
Thanks
find_by_sql will return an ActiveRecord object only if you call it with YourModel.find_by_sql.
Why not use the ActiveRecord query interface. It does a good job with calculations.
UPDATED
#searches = #searches.group(:followable_id).limit(3).offset(0).count(:follower_id) if params[:only_famous_projects]
Notice that it will give you a Hash containing the count for each followable_id.
Isn't LIMIT 0, 3 equivalent to LIMIT 3 ?
COUNT will always return a FixNUM, because you asked the database to count the number of rows.
You should really try to use find_by_sql as a last resort as it is only meant to bypass ActiveRecord for things that it can not do. And even for things that ActiveRecord doesn't support, you can always see if you can use the Squeel or Valium gems to handle edge-cases.
Another reason not to use find_by_sql is that, for example, using MySQL specific terms will lock you out of using other databases in the future.

how active record sample works?

I happened to see a line
Item.where(conditions).limit(10).order('created_at desc')
And I wonder it is similar to
Item.where(conditions).order('created_at desc').limit(10)
Seems ok as per new changes to rails 3, active record
But how about if we want, sample 10 items, *ordered by created_at*. This was my question #1
question 2 is 'limit' works within query, 'sample' does not..right?, it seems to be taken care of ruby array sample..right?
I recommend you check the railscast episodes 239 active relation walkthrough to understand how it works.
Basically, the internal work is done by Arel gem, activerecord object simply maintains the arel object when you do chaining. By the end of the day, it lets arel generate the sql statement, then it calls the find_by_sql with the generated sql statement.
The thing is that whether you called order before limit or the other way round, the generated sql statement is simply the same. Everything is executed by the database backend, activerecord simply parse the results and build up objects back.

how do I write SQL in a ruby method?

I would like to have a method called feed in my User model that returns all the entries from two tables (discussions, exchanges).
In User.rb
def feed
SELECT * FROM discussions, exchanges GROUP BY created_at
end
This doesn't work, i get a problem in rails console
syntax error, unexpected ';', expecting '='
Can anyone show me how to write SQL in here? Basically I want to return and sort entries from two different tables..
if you want actual ActiveRecord objects you can try the following
def feed
exchanges = Exchange.all
discussions = Discussion.all
(exchanges + discussions).sort! { |a, b| a.created_at <=> b.created_at }
end
this is quite ineffective, as the sorting could be done in sql, but ActiveRecord cannot instantiate records selected from different tables (you can somehow override this by using STI)
Firstly - you can't just write plain SQL in your ruby code and expect it to work.
It's ruby, not SQL. They are different languages.
If you can - use the ruby-way with associations instead (as per the other example).
However - if you desperately need to use raw SQL (eg you have legavy tables that don't match to models or have some complex combination-logic in teh SQL that doesn't easily map to assocations); then you need to pass SQL to the database... which means using a connection via Active Record.
Try:
def feed
ActiveRecord::Base.connection.execute("SELECT * FROM discussions, exchanges GROUP BY created_at")
end
It will not return ruby models for you - just a raw results-object.
I'd recommend trying this in script/console and then doing a "puts my_user.feed.inspect" to have a look at the kind of thing it returns so you know how to use it.
Note: the presence of this kind of thing is considered a strong code smell - only use it where you really need it