I've got two related ActiveObject models:
class Product < ActiveRecord::Base
belongs_to :product_type
#contains a field called name
end
class ProductType < ActiveRecord::Base
has_many :products
# contains a field called name
end
If I join both models using their relationship, it all works well:
Product.joins(:product_type)
"SELECT \"products\".* FROM \"products\" INNER JOIN \"product_types\" ON \"product_types\".\"id\" = \"products\".\"product_type_id\""
But now I need to get the Products that have an specific ProductType, and what I know is the product type is its name (each product type's name is unique), so I've tried using conditions in the joined tables, as the Rails guides suggests:
Product.joins(:product_type).where(:product_type => {:name => "MyProductTypeUniqueName"})
which produces the following SQL:
"SELECT \"products\".* FROM \"products\" INNER JOIN \"product_types\" ON \"product_types\".\"id\" = \"products\".\"product_type_id\" WHERE \"product_type\".\"name\" = 'MyProductTypeUniqueName'"
Unfortunately it fails with the following error:
ActiveRecord::StatementInvalid: PG::Error: ERROR: missing FROM-clause entry for table "product_type"
The error seems to happen because the product_type columns are not included in FROM clause when the SQL is generated ( at least the 'product_type.name' column isn't)
Using PostgreSQL and Rails 3.2.3
SQL results are provided using the to_sql method for the ActiveRecord::Relation object instance returned
I know that I can make a join directly providing the sql string but this should work as it is an example almost equal to the one in the rails guides.
What am I doing wrong?
EDIT: Both models contains a field called 'name'. Included in case this is the reason the SQL is not building correctly.
try this,
Product.joins(:product_type).where(:table_name => {:name => "MyProductTypeUniqueName"})
where :table_name is the name of the table for :product_type in the databse
Related
I have a many-to-many self join table called people that uses the following model:
class Person < ApplicationRecord
has_and_belongs_to_many :children,
class_name: "Person",
join_table: "children_parents",
foreign_key: "parent_id",
association_foreign_key: "child_id",
optional: true
has_and_belongs_to_many :parents,
class_name: "Person",
join_table: "children_parents",
foreign_key: "child_id",
association_foreign_key: "parent_id",
optional: true
end
If it isn't apparent in the above model - in addition to the people table in the database, there is also a children_parents join table with two foreign key index fields child_id and parent_id. This allows us to represent the many-to-many relationship between children and parents.
I want to query for siblings of a person, so I added the following method to the Person model:
def siblings
self.parents.map do |parent|
parent.children.reject { |child| child.id == self.id }
end.flatten.uniq
end
However, this makes three SQL queries:
Person Load (1.0ms) SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."parent_id" WHERE "children_parents"."child_id" = $1 [["child_id", 3]]
Person Load (0.4ms) SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."child_id" WHERE "children_parents"."parent_id" = $1 [["parent_id", 1]]
Person Load (0.4ms) SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."child_id" WHERE "children_parents"."parent_id" = $1 [["parent_id", 2]]
I know that it is possible to make this a single SQL query like so:
SELECT DISTINCT(p.*) FROM people p
INNER JOIN children_parents cp ON p.id = cp.child_id
WHERE cp.parent_id IN ($1, $2)
AND cp.child_id != $3
$1 and $2 are the parent ids of the person, and $3 is the person id.
Is there a way to do this query using ActiveRecord?
You can use something like this:
def siblings
Person.select('siblings.*').from('people AS siblings').where.not(id: id)
.where(
parents.joins(
'JOIN children_parents ON parent_id = people.id AND child_id = siblings.id'
).exists
)
end
Here you can see few strange things:
from to set table alias. And you should avoid this, because after such table aliasing active record will not help any more with column names from ruby: where(column: value).order(:column) - will not work, only plain sql strings are left
exists - I use it very often instead of joins. When you are joining many records to one, you are receiving duplicates, then comes distinct or group and new problems with them. Exists also gives isolation of query: table and columns in EXISTS expression are invisible for other parts of query. Bad part of using it in rails: at least 1 plain SQL is needed.
One weakness of this method: if you will call it for each record somewhere, then you will have 1 query for each record - N+1 problem.
Now few words about The Rails Way. Rails guide suggests to always use has_many :through instead of habtm, I seen it here: https://github.com/rubocop-hq/rails-style-guide.
Rails ideology as I understood it stands for speed of development and simplicity of maintenance. First means that performance does not matter (just imagine how many users you need to start have issues with it), second says that flexibility of plain SQL is good, but not in rails, in rails please make code as simple as possible (see rubocop defaults: 10 loc in method, 100 loc in class, and 4 complexity metrics always saying that your code is too complex). I mean, many real world rails projects are making queries with N+1, making ineffective queries, and this rarely becomes a problem
So, in such cases I would recommend to try includes, preload, eager_load.
I have a Recipe class and an Ingredient class which are connected with a has_many to a has_many through a join table RecipeIngredients. I am trying to create some filters and am trying to sort my Recipes by the number of Ingredients they contain. I cannot figure out the proper SQL, I am also trying to use Arel to find my answer. But I will take any proper way to query for this at this point. In reverse, I will also be trying to Query the Ingredients got how many Recipes they are in.
Thank you in advance for any help anyone can offer, I am having trouble with my queries and completely ran out of all thoughts for tonight. Thanks.
I'd consider using Arel for this kind of problem to be overly complicated. ActiveRecord itself, which is a layer above Arel can solve this quite comfortably.
I assume you have the following models
class Recipe
has_many :recipe_ingredients
...
end
class RecipeIngredient
has_one: :recipe
has_one: :ingredient
...
end
class Ingredient
has_many :recipe_ingredients
...
end
In order to get the recipes ordered by the number of ingredients you'll have to generate the following SQL statement:
SELECT
recipes.id
...
, recipes.[last_column_name]
# optionally
, COUNT(*) ingredients_count
FROM
recipes
OUTER JOIN
recipe_ingredients
ON
recipe_ingredients.recipe_id = recipe.id
GROUP BY
ingredient_count DESC
which can be done by
Recipe
.joins(:recipe_ingredients)
.group(Recipe.column_names)
.select(Recipe.column_names, 'COUNT(*) ingredients_count')
.order('ingredients_count DESC') # Or ASC
The Recipe instances returned will be sorted by the number of ingredients. They will also have an additional method ingredients_count which returns the number of ingredients.
This could also be placed into a scope inside the Recipe class.
def self.ordered_by_ingredients_count
joins(:recipe_ingredients)
.group(column_names)
.select(column_names, 'COUNT(*) ingredients_count')
.order('ingredients_count DESC') # Or ASC
end
For the reverse, number of recipes an ingredient is in, just swap some names:
Ingredient
.joins(:recipe_ingredients)
.group(Ingredient.column_names)
.select(Ingredient.column_names, 'COUNT(*) recipe_count')
.order('recipe_count DESC') # Or ASC
I want to select a count of all surveys where the survey.property.address.city == "Garrison". I have the following models:
Survey
many_to_one :property
Property
one_to_many :surveys
many_to_one :address
Address
one_to_many :properties
How do I query using SQL?
SELECT count(*) FROM surveys JOIN...
Assuming that your table is named like rails would name those objects and you have the foreign keys that are implied by your relations:
SELECT
COUNT(*)
FROM
surveys
JOIN
properties ON surveys.property_id = properties.id
JOIN
addresses ON addresses.id = properties.address_id
WHERE
addresses.city = 'Garrison'
Also your relations are strangely defined... I'm assuming that that is just a psuedocode version to express the relations.
edit: I corrected the second join, because I believe I had the relations backwards.
I have a has_many (through accounts) association between the model User and the model Player.
I would like to know what is the best way to query all the users, and for each returned record get the associated players usernames attributes (in comma separated values).
So, if for example, a User named 'John' is associated with 3 players with usernames 'john_black', 'john_white', 'john_yellow'. I would like the query to return not just the User attributes but also an attribute called, for example, player_username, that would have this value: john_black, john_white, john_yellow.
I have tried the following Arel query in the User model:
select(`users`.*).select("GROUP_CONCAT(`players`.`username` SEPARATOR ',') AS comma_username").joins(:user)
.joins(:accounts => :player )
Which gives me the following SQL:
SELECT `users`.*, GROUP_CONCAT(`players`.`username` SEPARATOR ',') AS comma_username FROM `users` INNER JOIN `accounts` ON `accounts`.`user_id` = `users`.`id` INNER JOIN `players` ON `players`.`id` = `accounts`.`player_id`
If I execute this in MySQL console it works, but if I try to fetch from the model, I don't get the comma separated values.
What am I missing?
I believe ActiveRecord maps all columns retrieved by the SQL query with all attributes you have on your model, which in most of the cases are exactly the same. Maybe if you create a virtual accessor on your model, ActiveRecord could map your virtual column to the virtual attribute.
class User
attr_accessible :player_username
...
Give it a try and see if this works.
I have a legacy database that contains two different tables (tbl_players and tbl_player_ratings) that link to one another on a common column (player_key).
My problem: With Rails3, when I attempt to retrieve PlayersRatings joined to Players, only columns from PlayerRatings are returned. However, if I execute the same SQL query at the mysql command line, columns from both tables are returned.
For simplicity, I here provide just a few of the columns from each table.
tbl_players
player_key, last_name
tbl_player_ratings
player_key, rating
My Rails classes representing these tables look as follows:
class PlayerRating < ActiveRecord::Base
establish_connection :legacy
set_table_name 'tbl_player_ratings'
set_primary_key "player_key"
belongs_to :player,
:foreign_key => 'player_key'
end
class Player < ActiveRecord::Base
establish_connection :legacy
set_table_name 'tbl_players'
set_primary_key "player_key"
has_many :player_ratings,
:foreign_key => 'player_key'
end
The query that I'm running in the rails console:
PlayerRating.joins(:player).select("*").limit(1)
This returns a sole PlayerRating without any Player fields represented.
The SQL produced by the above rails command:
SELECT * FROM `tbl_player_ratings` INNER JOIN `tbl_players` ON `tbl_players`.`player_key` = `tbl_player_ratings`.`player_key` LIMIT 1
When I execute that exact command at the mysql command-line, all columns in both tables are returned.
Why does Rails and ActiveRecord not do the same (return all columns in both tables)?
A PlayerRating is only a PlayerRating object; you cannot coerce it into being a combination PlayerRating-Player via a joins(:player) on the query.
Following that query, you will have access to the player via player_rating.player.
array_of_player_ratings = PlayerRating.joins(:player).limit(any_number)
single_player_rating = PlayerRating.joins(:player).first
player = single_player_rating.player
Do look at the duplicate question linked for a fuller description of the difference between SQL queries and ActiveRecord queries.