ActiveRecord- Inner Join two SQL tables + Where clauses - mysql

I have two tables in MySQL and I would like to call a SQL query using an inner join and multiple Where clauses based on a form's input. I am aware that you can execute a raw SQL Query via ActiveRecord::Base.connection, but I'd like to learn how to do so using ActiveRecord objects. The schema for the two MySQL tables are like so:
Describe Options;
'VIN','varchar(45)','NO','PRI',NULL,''
'LEATHER','varchar(45)','YES','',NULL,''
'4WD','varchar(45)','YES','',NULL,''
'FOGLIGHTS','varchar(45)','YES','',NULL,''
'DVD','varchar(45)','YES','',NULL,''
'SURROUND','varchar(45)','YES','',NULL,''
and
Describe Inventory;
'VIN','varchar(30)','NO','PRI',NULL,''
'MAKE','varchar(30)','NO','',NULL,''
'MODEL','varchar(30)','NO','',NULL,''
'TYPE','varchar(50)','NO','',NULL,''
I would like to execute a SQL script like so:
Select Inventory.* from Inventory
INNER JOIN Options
ON Inventory.VIN = Options.VIN
WHERE Inventory.Make = "Toyota"
AND Options.Leather = "Yes";
My Ruby classes in ActiveRecord are like so:
class Option < ActiveRecord::Base
self.table_name = "Options"
end
class Inventory < ActiveRecord::Base
self.table_name = "INVENTORY"
end
Again, I know that I can just input the query as a script, but I'd like to learn how to do it via Ruby best practices

You can change into this:
Inventory.joins("INNER JOIN Options ON Inventory.VIN = Options.VIN")
.where("Inventory.Make = ? AND Options.Leather = ?", "Toyota", "YES")
In my opinion, I'd say that you have to change you table into inventories and options for model Inventory and Option so you don't need to use set_table_name in each model. It's about rails convention style code. Then you can see the model like this.
class Inventory < ActiveRecord::Base
has_many :options, foreign_key: "VIN"
end
class Option < ActiveRecord::Base
belongs_to :inventory, foreign_key: "VIN"
end
I hope this help you.

Related

How to search in Active Record with the condition A + B

I would like to specify each condition for Maintenance and Ability associated with a Person and retrieve each of them.
Normally, the query would be to retrieve the records that contain the two conditions, Person.maintenances and Person.abilities.
A = Person.maintenances.where(~~~~)
B = Person.abilities.where(~~~)
I want to get all of the above in a single query as a Person.
(What I want = A + B in a single query)
Also, I don't want to have the N+1 problem, but I would like to know how to prevent the query from being issued because the conditions of Person.maintenances and Person.abilities are separated.
class Person << ApplicationRecord
has_many :maintenances
has_many :abilities
end
class Maintenance << ApplicationRecord
belongs_to :person
end
class Ability << ApplicationRecord
belongs_to :person
end
※I'm using MySQL
Thanks.
A simple join should do the trick:
Person.joins(:maintenance).where(maintenances: {status: 0, example: "yes"}).joins(:ability).where(abilities: {........})
Or something like this, which should create the same result
Person.where(maintenances: Maintenance.where(person: self, ......), abilities: Ability.where(person: self, ......))

convert mysql query into rails query

Hi all I have a problem converting mysql query into rails query.
I have these models -
class User < ApplicationRecord
has_many :comments, foreign_key: "commenter_id"
end
class Comment < ApplicationRecord
belongs_to :commenter, class_name: "User"
end
Can anyone help me out with converting following query into rails query-
UPDATE comments
INNER JOIN users on comments.commenter_id = users.id
SET comments.deleted_at = users.deleted_at
WHERE users.deleted_at IS NOT NULL
I am trying to make soft-delete comments whose commenter was softly deleted.
UPDATE 1:
so far I can able to do it by using this-
User.only_deleted.includes(:comments).find_each do |u|
u.comments.update_all(deleted_at: u.deleted_at)
end
But I want to do this on single query without having to iterate over the result.
UPDATE 2:
I am using acts_as_paranoid gem, so unscoping user is needed and my final query became:
User.unscoped{Comment.joins(:commenter).where.not(users: {deleted_at: nil}).update_all("comments.deleted_at = users.deleted_at")
This should work on MySQL:
Comment
.joins(:user)
.where.not(users: { deleted_at: nil })
.update_all("comments.deleted_at = users.deleted_at")
This won't work on Postgres since its missing a FROM clause for users.
A less performant but polyglot option is:
Comment
.joins(:user)
.where.not(users: { deleted_at: nil })
.update_all("deleted_at = ( SELECT users.deleted_at FROM users WHERE comments.id = users.id )")
This is still probably an order of magnitude better than iterating through the records in Ruby since you eliminate the traffic delay between your app server and the db.
From your comments, I think this is what you want:
Comment.where.not(user_id: nil).each { |comment| comment.update_attributes(deleted_at: comment.user.deleted_at)
Or slightly more readable:
Comment.all.each do |comment|
next unless comment.user.present?
comment.update_attributes(deleted_at: comment.user.deleted_at)
end
The code below should execute number of queries corresponding to deleted_users and without loading User and any associated Comments in memory
deleted_users_data_arr = User.only_deleted.pluck(:id, :deleted_at)
deleted_users_data_arr.each do |arr|
deleted_user_id = arr[0]
user_deleted_at = arr[1]
Comment.where(commenter_id: deleted_user_id).update_all(deleted_at: user_deleted_at)
end

Rails Active Record: How to join three tables?

I've three tables as shown below:
Advertiser model:
class Advertiser < ActiveRecord::Base
has_many :advertisers_account_groups
AdvertisersAccountGroup model
class AdvertisersAccountGroup < ActiveRecord::Base
belongs_to :advertiser
belongs_to :v2_account_account_group, class_name: 'V2Account::AccountGroup', foreign_key: 'account_group_id'
I wanna know which advertiser belongs to v2_account_account_groups
and wanna get v2_account_account_groups.name
Desired Output:
What I tried;
Advertiser.where(media_type: "line").joins(advertisers_account_groups,v2_account_account_groups)
But it doesn't work
It looks to me that your current setup uses AdvertisersAccountGroup as a join table; therefore, I'd suggest using a has_many :through association.
To do this, you'd just need to switch up the models as follows:
class Advertiser < ActiveRecord::Base
has_many :v2_account_account_groups, through: :advertisers_account_groups
has_many :advertisers_account_groups
...
end
class V2Account::AccountGroup < ActiveRecord::Base
has_many :advertisers, through: :advertisers_account_groups
has_many :advertisers_account_groups
...
end
class AdvertisersAccountGroup < ActiveRecord::Base
belongs_to :advertiser
belongs_to :v2_account_account_group, class_name: 'V2Account::AccountGroup', foreign_key: 'account_group_id'
...
end
This will allow you to query against the advertiser as desired, i.e. advertiser.v2_account_account_groups.
However, this association is a many-to-many between advertisers and v2 account groups as is - therefore, you won't be able to call advertiser.v2_account_account_groups.name as advertiser.v2_account_account_groups returns a collection rather than a single record.
You could use advertiser.v2_account_account_groups.map(&:name) (to get an array of all groups' names) or advertiser.v2_account_account_groups.first&.name, but it sounds as if you might need to restructure the data if an advertiser should have just the one v2 account group.
Does that make sense and sound like what you're looking for? Let me know if you've any questions.
Edit:
Based on your comment, I think you should be able to construct a query as follows:
Advertiser.includes(advertiser_account_groups: : v2_account_account_group)
.where(advertiser_account_groups: { v2_account_groups: { name: "something" } })
Does that sound like what you're looking for?
A couple of things to note:
when referencing the associations in the includes, you want to use the association name
however, when plugging these into the where clause, you need to use the full table names, as they are in the databases (searchable via Model.table_name)
Also, in your comment, you reference adding media_type: "line", which the below also includes:
Advertiser.includes(advertiser_account_groups: : v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } })
Probably the best way to structure this in your code is as a scope in your advertiser model, such as:
scope :by_v2_group_name, -> (name) { includes(advertiser_account_groups: :v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } }) }
or
scope :by_v2_group_name, (lambda do |name|
includes(advertiser_account_groups: :v2_account_account_group)
.where(media_type: "line", advertiser_account_groups: { v2_account_account_groups: { name: "something" } })
end)
That will then allow you to keep your code clean and call Advertiser.by_v2_group_name("something").
Let me know how you get on with that and we'll work on it as needed :)
This answer is relevant only if you don't need a model for the join table
advertiser_account_gourps
(if you don't have any extra attributes other then advertiser_id and account_gourp_id on that join table - you usually don't need it)
While #SRack's answer is using the has_many :through association, I think you can use a simple many-to-many association (has_and_belongs_to_many) because in your images i can see that the join table only have the ids of the associated tables (Unless you do need the id column -> and in that case, use #SRack's solution!)
class Advertiser < ActiveRecord::Base
has_and_belongs_to_many :advertisers_account_groups
class AccountGroup < ActiveRecord::Base
has_and_belongs_to_many :advertisers
then you can get them using:
advertiser = Advertiser.first
advertiser.account_groups #to get account groups of that advertiser
and vice versa:
account_group = AccountGroup.first
account_group.advertisers # to get advertisers of that account group
#SRack's comment is correct! i changed it to HABTM association!
In that case you will need to create a migration to make the join table:
rails g migration CreateJoinTableAdvertiserAccountGroup advertiser account_group
This table will only include the associated advertiser_id and the account_group_id.
You don't need to create a model and handle stuff on that table, rails will fill that table for you.

Rails 4 - how to generate statistics through "joins" and "includes" commands in Activerecord?

I kind of stuck at trying to generate statistics for my application. The relevant part of the application has the following structure:
class CarRegistration < ActiveRecord::Base
belongs_to :ride
belongs_to :car
...
end
class Car < ActiveRecord::Base
has_many :car_registration
...
end
class Ride < ActiveRecord::Base
belongs_to :passenger
belongs_to :driver
has_many :car_registration
...
end
class Driver < ActiveRecord::Base
has_many :cars
...
end
class Passenger < ActiveRecord::Base
has_many :cars
...
end
I am trying to get a list of rides, top drivers and and top passengers. I originally tried something like this:
#rides_finished = Ride.joins(:car_registration)
.select('rides.id')
.where("(car_registrations.ride_id = rides.id)
AND rides.status = 3
AND rides.driver_currency = ?
AND rides.passenger_currency = ?", currency, currency)
.distinct # against displaying one shipment multiple times
And then I tried:
#top_pasengers = #rides_finished.joins(:passenger)
.select('passengers.id, passengers.name, count(rides.passenger_id) AS count_all')
.where('rides.passenger_id IS NOT NULL')
.group('passengers.id')
.order('count_all DESC')
.limit(10)
But when I run these queries, I get
Mysql2::Error: Unknown column 'count_all' in 'order clause': ...
Any help how to get the needed numbers?
Thank you very much
Your question is a little confusing because your query uses Ride but there is no Ride in the model definitions listed. I've focussed purely on the example queries you listed.
I think it would be easier to start with a single query chain for 'top passengers':
Passenger
.select('passengers.*')
.select('count(1) as ride_count')
.joins(:rides)
.where(rides: { status: 3,
driver_currency: currency,
passenger_currency: currency })
.group('passengers.id')
.order('ride_count desc')
.limit(10)
That will get you an ActiveRecord::Relation of Passenger models that also respond to a ride_count call, e.g. you could use it like:
results.each do |p|
puts "#{p.name}: #{p.ride_count}'
end
If all that works, you should be able to adjust the query to get the top drivers.
To get the list of finished rides, I suggest a separate, simple query:
Ride.where(status: 3,
driver_currency: currency,
passenger_currency: currency)
Let me know if any of that produces an error.

ActiveRecord custom has_many association makes multiple calls to the database

I have a pair of ActiveRecord objects that have a belongs_to ... has_many association, with the has_many association being custom-made. Example:
First AR object:
class Car < Vehicle
has_many :wheels, class_name: "RoundObject", foreign_key: :vehicle_id, conditions: "working = 1"
validates_presence_of :wheels
...
end
Second AR object:
class RoundObject < ActiveRecord::Base
belongs_to :vehicle
...
end
Please note that the above is not indicative of my app's function, simply to outline the association between my two AR objects.
The issue I'm having is that, when I reset the cache (and thus my Rails app re-caches all AR objects in the database), when it comes time for the RoundObject object to get re-cached, it makes multiple calls to the database, one for each unique vehicle_id associated with the collection of RoundObjects. The SQL commands being run are output to the console, so this is what my output looked like:
RoundObject Load (2.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 28 AND (active = 1)
RoundObject Load (1.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 29 AND (active = 1)
RoundObject Load (2.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 30 AND (active = 1)
My app has several other AR objects that use the built-in has_many association without any modifications, and I notice that they only hit the database once when resetting the cache. For instance:
Micropost Load (15.0ms) SELECT `microposts`.* FROM `microposts` INNER JOIN `posts` ON `posts`.`id` = `microposts`.`post_id` WHERE `microposts`.`active` = 1 AND `posts`.`active` = 1
My question is, how can I make my AR object only hit the database once on cache reset, while still maintaining the custom has_many association I need? Can I manually force a join on the SQL query being called, and will this help?
Thank you!
You can use includes method while calling your Vehicle object to include the RoundObject.
It will go like this:
Vehicle.where(conditions_for_getting_data).includes(:round_object)