Join multiple tables rails sql query - sql

class Account
has_many :metadata
class Keys
has_many :metadata
class Metadatum
belongs_to :key
belongs_ to :account
In Metadatum object I keep additional information about Account, for exapmle 'age'.
In Key object I keep information about type of Metadatum.
Metadatum table:
id
key_id
account_id
value
Key table:
name
data_type
I want to search accounts by multiple metadata. For example
Metadatum with value = '18' that belongs to Key with name = 'age'
Metadatum with value = 'John' that belongs to Key with name = 'first_name'
My query is:
accounts.joins(metadata: :key).where("keys.name = ? AND
metadata.value = ?", params[:key], params[:value]).where("keys.name
= ? AND metadata.value = ?", params[:key1], params[:value1])
It is wrong because in my opinion it looks for an Account that has Metadatum with both key_ids and values. No such Metadatum exists - each has only one key_id and value.
What would be the right query?

If you want accounts that comply with both conditions, I would try:
first = Account.joins(metadata: :key).where("keys.name = ? AND metadata.value = ?", params[:key], params[:value])
final = first.joins(metadata : :key).where("keys.name
= ? AND metadata.value = ?", params[:key1], params[:value1])
New try (really ugly :))
first_ids = Account.joins(metadata: :key).where("keys.name = ? AND metadata.value = ?", params[:key], params[:value]).pluck(“accounts.id”)
final = Account.where(id: first_ids).joins(metadata : :key).where("keys.name = ? AND metadata.value = ?", params[:key1], params[:value1])

Try following
accounts.joins(metadata: :key)
.where("(keys.name = ? AND metadata.value = ?) OR
(keys.name = ? AND metadata.value = ?)",
params[:key], params[:value], params[:key1], params[:value1])

Related

no id for model?

I get the error
undefined local variable or method `id' for #<Class:0x007fe1dc4e3bb0>
when defining a sql call in my model user:
RECENT_EVENTS_CONDITION = "(user_id = #{id}) OR user_id IN (SELECT user_b_id AS user_id FROM user_follows WHERE user_follows.user_a_id = #{id} )"
Model code
has_many :recent_events,
:class_name => "Activity",
:finder_sql => 'SELECT activities.* FROM activities
WHERE ' + RECENT_EVENTS_CONDITION + '
ORDER BY activities.created_at DESC',
:counter_sql => 'SELECT COUNT(*) FROM activities
WHERE ' + RECENT_EVENTS_CONDITION
Everything works fine until i introduce this recent_events ...
I'm assuming this is a User class or something similar, and I'm assuming when you use #{id}, you're really just trying to use the user's id. So I would do this instead:
RECENT_EVENTS_CONDITION = "(user_id = users.id) OR user_id IN (SELECT user_b_id AS user_id FROM user_follows WHERE user_follows.user_a_id = users.id )"

pulling one field of a record into an array in a Rails 3 app

Rails 3 noob here. Currently the code in my controller below is getting the whole record in my database. I am trying to populate the array with one integer, not the whole record. The integer is contained in a table "answers" and the field name is "score". How can i modify this line in my controller to get just the one field?
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id )
end
UPDATE FOR CLARIFICATION: The :score can be any integer from 0 to 5. I would like to populate #ans[s.position] with the integer.
Thanks.
You're very close
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? and user_id = ?",s.q_id,current_user.id).select(:score).first.try(:score)
end
You need to select "score" from Answer, then you need to retrieve it from the object.
Since where could potentially return many Answers, use first to pull off the first Answer, and then score to project out the score field.
answers = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id )
score = answers.first.score
All together it would be:
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id ).first.score
end
As an optimization to only retrieve score from the database, instead of answers.*, you could use select(:score).
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id ).select(:score).first.score
end
See Active Record Query Interface for more about using select.
Just include them.
#questions = Question.includes(:answers).select(:position)
Or use this
#questions = Question.select(:position)
#questions.each{ |s| #ans[s.position] = s.answers.where(:user_id => current_user.id) }

Rails3/ActiveRecord: select sum with parameters?

Give the following (already simplified) query in SQLite:
def self.calculate(year, month, user_id, partner_id)
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {
:user_id => user_id,
:partner_id => partner_id
}).
where('entries.date <= :last_day', {
:last_day => Date.new(year, month, 1).at_end_of_month
}).
select('sum(case when joint = "f" and user_id = :user_id then amount_calc else 0 end) as sum_single' , {
:user_id => user_id
}).
group("strftime('%Y-%m', date)")
end
The full query has more sums with different case when statements and some of them depend on whether it is user_id oder partner_id. Unfortunately, Rails complains as select does not take the second parameter with the substitutions like where does. Is there any way to achieve what I want without running two queries, one for user_id and one for partner_id?
One can be so blind....instead of:
select('sum(case when joint = "f" and user_id = :user_id then amount_calc else 0 end) as sum_single' , {
:user_id => user_id
}).
just build the string:
select('sum(case when joint = "f" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_single').
As nobody answered, this is for the archives :)
Edit: Sorry, beware of that: as noted below, this is vulnerable.

Rails: join with multiple conditions

I have a simple model like
class Interest < ActiveRecord::Base
has_and_belongs_to_many :user_profiles
end
class UserProfile < ActiveRecord::Base
has_and_belongs_to_many :interests
end
When I want to query all the users with a specific interests, that's fairly simple doing
UserProfile.joins(:interests).where('interests.id = ?', an_interest)
But how can I look for users who have multiple interests? Of course if I do
UserProfile.joins(:interests).where('interests.id = ?', an_interest).where('interests.id = ?', another_interest)
I get always an empty result, since after the join, no row can have simultaneously interest.id = an_interest and interest.id = another_interest.
Is there a way in ActiveRecord to express "I want the list of users who have 2 (specified) interests associated?
update (solution) that's the first working version I came up, kudos to Omar Qureshi
specified_interests.each_with_index do |i, idx|
main_join_clause = "interests_#{idx}.user_profile_id = user_profiles.id"
join_clause = sanitize_sql_array ["inner join interests_user_profiles interests_#{idx} on
(#{main_join_clause} and interests_#{idx}.interest_id = ?)", i]
relation = relation.joins(join_clause)
end
in (?) is no good - it's an OR like expression
what you will need to do is have multiple joins written out longhanded
profiles = UserProfile
interest_ids.each_with_index do |i, idx|
main_join_clause = "interests_#{idx}.user_profile_id = user_profiles.id"
join_clause = sanitize_sql_array ["inner join interests interests_#{idx} on
(#{main_join_clause} and interests_#{idx}.id = ?)", i]
profiles = profiles.join(join_clause)
end
profiles
You may need to change the main_join_clause to suit your needs.
This will get users that have at least one of the specified interests.
UserProfile.joins(:interests).where(:id => [an_interest_id, another_interest_id])
To get users that have both of the specified interests I'd probably do something like this:
def self.all_with_interests(interest_1, interest_2)
users_1 = UserProfile.where("interests.id = ?", interest_1.id)
users_2 = UserProfile.where("interests.id = ?", interest_2.id)
users_1 & users_2
end
Not amazingly efficient, but it should do what you need?
Try IN (?) and an array:
UserProfile.joins(:interests).where('interests.id IN (?)', [1,2,3,4,5])

Combining conditions in find. (Rails)

Now i am inputting some data from a form and i have a code to search the database inputting several parameters as input conditions. Now if one the parameters is null (i.e) the field is unchecked i need to replace that parameter with something say * so that the search query is unaffected. How would i do that?
#report = Problem.find(:all, :conditions => ["problems.cause_id = ? and problems.location_id = ? and problems.device_id = ? and problems.priority_id = ?", Report.find_by_id(params[:id]).cause_id, Report.find_by_id(params[:id]).location_id, Report.find_by_id(params[:id]).device_id, Report.find_by_id(params[:id]).priority_id])
It would be better to not have that condition at all than to use *. In this case it's simple as all of your comparison operators are "=". That means you can use the hash form of conditions. Your code is also quite inefficient as you load the same report object 3 or four times. Your question about one of the params being null doesn't make sense for this reason: you just use the same param again and again. Also you set a variable called #report to be a Problem object which is confusing.
#report = Report.find_by_id(params[:id])
conditions = {:cause_id => #report.cause_id, :location_id => #report.location_id, :device_id => #report.device_id, :priority_id => #report.priority_id}
conditions.delete_if{|k,v| v.blank?}
#problem = Problem.find(:all, :conditions => conditions)
rep = Report.find_by_id(params[:id])
cause = rep.cause_id ? rep.cause_id : '*'
location = rep.location_id ? rep.location_id : '*'
device = rep.device_id ? rep.device_id : '*'
priority = rep.priority_id ? rep.priority_id : '*'
#report = Problem.find(:all,
:conditions => ["problems.cause_id = ? and
problems.location_id = ? and
problems.device_id = ? and
problems.priority_id = ?",
cause, location,
device, priority
]
)