I am trying to allow my Rails App some advanced functionality in way of creating relationships. I am running MySQL as the back-end. The query below runs just fine in PHPMyAdmin, but upon attempted execution in rails, it does nothing, no errors and no database writes....
I use active record to create the relationships between Campaign and Location, and then the query below should work to build afterward in campaign_metros table:
class CampaignLocation < ActiveRecord::Base
belongs_to :campaign
belongs_to :location
after_touch :create_campaign_metro
private
def create_campaign_metro
#sql_insert="INSERT INTO `campaign_metros` (`campaign_id`,`metro_id`, `created_at`,`updated_at`)
SELECT f.`campaign_id`, f.`city_id`, NOW(), NOW()
FROM (SELECT d.`campaign_id`, d.`city_id`
FROM (SELECT c.`campaign_id`, c.`city_id`
FROM (SELECT distinct a.`campaign_id`, b.`city_id`
FROM `campaign_locations` a
INNER JOIN `locations` b
ON a.`location_id` = b.`id`) c) d
LEFT JOIN `campaign_metros` e
ON e.`campaign_id` = d.`campaign_id` and
e.`metro_id` = d.`city_id`
WHERE e.`metro_id` is null and e.`campaign_id` is null) f;"
ActiveRecord::Base.connection.execute(#sql_insert)
end
end
I know from MySQL DELETE FROM with subquery as condition, in answer number two by CodeReaper, that MySQL requires another level of aliases when dealing with subqueries, but running my query in Rails does nothing at all.
How can I get this to work?
Related
I am looking to run the following query in Rails (I have used the scuttle.io site to convert my SQL to rails-friendly syntax):
Here is the original query:
SELECT pools.name AS "Pool Name", COUNT(DISTINCT stakings.user_id) AS "Total Number of Users Per Pool" from stakings
INNER JOIN pools ON stakings.pool_id = pools.id
INNER JOIN users ON users.id = stakings.user_id
INNER JOIN countries ON countries.code = users.country
WHERE countries.kyc_flow = 1
GROUP BY (pools.name);
And here is the scuttle.io query:
<%Staking.select(
[
Pool.arel_table[:name].as('Pool_Name'), Staking.arel_table[:user_id].count.as('Total_Number_of_Users_Per_Pool')
]
).where(Country.arel_table[:kyc_flow].eq(1)).joins(
Staking.arel_table.join(Pool.arel_table).on(
Staking.arel_table[:pool_id].eq(Pool.arel_table[:id])
).join_sources
).joins(
Staking.arel_table.join(User.arel_table).on(
User.arel_table[:id].eq(Staking.arel_table[:user_id])
).join_sources
).joins(
Staking.arel_table.join(Country.arel_table).on(
Country.arel_table[:code].eq(User.arel_table[:country])
).join_sources
).group(Pool.arel_table[:name]).each do |x|%>
<p><%=x.Pool_Name%><p>
<p><%=x.Total_Number_of_Users_Per_Pool%>
<%end%>
Now, as you may notice, sctuttle.io does not include the distinct parameter which I need. How in the world can I use distinct here without getting errors such as "method distinct does not exist for Arel Node?" or just syntax errors?
Is there any way to write the above query using rails ActiveRecord? I am sure there is, but I am really not sure how.
Answer
The Arel::Nodes::Count class (an Arel::Nodes::Function) accepts a boolean value for distinctness.
def initialize expr, distinct = false, aliaz = nil
super(expr, aliaz)
#distinct = distinct
end
The #count expression is a shortcut for the same and also accepts a single argument
def count distinct = false
Nodes::Count.new [self], distinct
end
So in your case you could use either of the below options
Arel::Nodes::Count.new([Staking.arel_table[:user_id]],true,'Total_Number_of_Users_Per_Pool')
# OR
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
Suggestion 1:
The Arel you have seems a bit overkill. Given the natural relationships you should be able to simplify this a bit e.g.
country_table = Country.arel_table
Staking
.joins(:pools,:users)
.joins( Arel::Nodes::InnerJoin(
country_table,
country_table.create_on(country_table[:code].eq(User.arel_table[:country])))
.select(
Pool.arel_table[:name],
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
)
.where(countries: {kyc_flow: 1})
.group(Pool.arel_table[:name])
Suggestion 2: Move this query to your controller. The view has no business making database calls.
I have the following MySQL statement which does what I want:
SELECT scores.score, registrations.parade, AVG(scores.score) as result
FROM scores
JOIN registrations ON scores.registrationId=registrations.id
where registrations.parade=1
GROUP BY scores.registrationId
ORDER BY result DESC
basically, with sqlalchemy I think I would start with:
db.session.query(Scores, func.avg(Scores.score).label('result'))
This is because I do not need the information from registrations (it's linked to each other in the model). I only join the registrations in the MySQL statement because I need to filter on its parade.id
Below is what I have been trying so far but does not work:
scores = db.session.query(Scores,func.avg(Scores.score).label('result'))\
.filter(Registrations.parade == 1)\
.group_by(Scores.registrationId)\
.order_by(desc('result'))
You are missing the join
scores = db.session.query(Scores.score,func.avg(Scores.score).label('result'))\
.join(Registrations)\
.filter(Registrations.parade == 1)\
.group_by(Scores.registrationId)\
.order_by(desc('result'))
Another issue is, that you should have an aggretation function for registrations.parade or else include it in the group_by statement.
Hello guys.
I've an issue with a simple query.
Here we go, that's the code.
UPDATE user_resources AS ures
LEFT JOIN user_buildings as ub
ON ub.city_id = ures.city_id
INNER JOIN building_consumption AS bcons
ON bcons.resource_id = ures.resource_id
SET ures.quantity = ures.quantity - abs(FORMULA HERE that requires
building level and consumption at lvl 1 [default])
WHERE
(SELECT COUNT(id) FROM building_consumption AS bc2
WHERE bc2.building_id=ub.building_id) =
(SELECT COUNT(bc3.id) FROM building_consumption AS bc3
LEFT JOIN tmp_user_resources AS ures
ON ures.resource_id = bc3.resource_id
WHERE ures.city_id = ub.city_id
AND bc3.building_id=ub.building_id
AND bc3.quantity>0
AND IFNULL(ures.quantity, 0) - abs(FORMULA AGAIN);
I'll try to explain a bit.
As you can imagine, this is for a game.
Users (players) can has different buildings in different cities.
tab user_buildings
|id, city_id, buildings_id, level, usage|
A building can produce different resources
tab building_production
|id, building_id, resource_id, quantity_h|
but it can consume some resources too:
tab building_consumption
|id, building_id, resource_id, quantity_h|
Obviously a building cannot produce if there are not enough resources to consume for his job.
That's why I'm trying to compare WHERE SELECT COUNT how many resources it has to consume AND how many resources it can actually consume.
Mysql does NOT ALLOW to subquery same table inside an UPDATE stmt.
Using a cursor + loop is too much slow. I prefer to use different solution.
Temp table could be a solution but my problem now is how to update the temp table without triggers? (UPDATE + SELECT fires triggers and to avoid endless loops mysql block the query, and i can't pause/resume triggers because
IF ((#TRIGGER_CHECKS = FALSE)
OR (#TRIGGER_BEFORE_INSERT_CHECKS = FALSE))
AND (USER() = 'root#localhost')
THEN
LEAVE thisTrigger;
END IF;
is inside the trigger itself).
I am open to all your suggestions!
Thanks
P.S. The code must be inside a scheduled event.
I'd really like to do the following query with the help with active record
(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 1)
intersect
(select *
from people p join cities c join services s
where p.city_id = c.id and p.id = s.person_id and s.type = 2)
Problem is, first of all, mysql doesn't support intersect. However, that can be worked around of. The thing is that I can get active record to output anything even close to that.
In active record the best I could do was to issue multiple queries then use reduce :& to join them, but then I get an Array, not a Relation. That's a problem for me because I want to call things like limit, etc. Plus, I think it would be better to the intersection to be done by the database, rather than ruby code.
Your question is probably solvable without intersection, something like:
Person.joins(:services).where(services: {service_type: [1,2]}).group(
people: :id).having('COUNT("people"."id")=2')
However the following is a general approach I use for constructing intersection like queries in ActiveRecord:
class Service < ActiveRecord::Base
belongs_to :person
def self.with_types(*types)
where(service_type: types)
end
end
class City < ActiveRecord::Base
has_and_belongs_to_many :services
has_many :people, inverse_of: :city
end
class Person < ActiveRecord::Base
belongs_to :city, inverse_of: :people
def self.with_cities(cities)
where(city_id: cities)
end
def self.with_all_service_types(*types)
types.map { |t|
joins(:services).merge(Service.with_types t).select(:id)
}.reduce(scoped) { |scope, subquery|
scope.where(id: subquery)
}
end
end
Person.with_all_service_types(1, 2)
Person.with_all_service_types(1, 2).with_cities(City.where(name: 'Gold Coast'))
It will generate SQL of the form:
SELECT "people".*
FROM "people"
WHERE "people"."id" in (SELECT "people"."id" FROM ...)
AND "people"."id" in (SELECT ...)
AND ...
You can create as many subqueries as required with the above approach based on any conditions/joins etc so long as each subquery returns the id of a matching person in its result set.
Each subquery result set will be AND'ed together thus restricting the matching set to the intersection of all of the subqueries.
UPDATE
For those using AR4 where scoped was removed, my other answer provides a semantically equivalent scoped polyfil which all is not an equivalent replacement for despite what the AR documentation suggests. Answer here: With Rails 4, Model.scoped is deprecated but Model.all can't replace it
I was struggling with the same issue, and found only one solution: multiple joins against the same association. This may not be too rails-ish since I'm constructing the SQL string for the joins, but I haven't found another way. This will work for an arbitrary number of service types (cities doesn't seem to factor in, so that join was omitted for clarity):
s = [1,2]
j = ''
s.each_index {|i|
j += " INNER JOIN services s#{i} ON s.person_id = people.id AND s#{i}.type_id = #{s[i]}"
}
People.all.joins(j)
I have a time tracking application.
An Hour belongs_to a Project. And a Project belongs to a ProjectStatus and thus has a field project_status_id.
Now I want to scope my Hour model to filter all hours that belong to projects with a certain status, e.g. 2.
In my Project model I have this scope:
#project.rb
scope :continous, where(:project_status_id => '2')
In my Hour model I tried the following scope but it always returns an empty array, also I know there are hours that math my criteria:
#hour.rb
scope :intern, joins(:project) & Project.continous
also I think the generated SQL output on the console looks a little strange:
1.9.2-p180 :003 > Hour.intern
Hour Load (104.5ms) SELECT `hours`.* FROM `hours` INNER JOIN `projects` ON `projects`.`id` = `hours`.`project_id`
Project Load (6.1ms) SELECT `projects`.* FROM `projects` WHERE (project_status_id <> 2)
Hour Load (95.9ms) SELECT `hours`.* FROM `hours` INNER JOIN `projects` ON `projects`.`id` = `hours`.`project_id`
Project Load (1.0ms) SELECT `projects`.* FROM `projects` WHERE `projects`.`project_status_id` = 2
Hour Load (92.6ms) SELECT `hours`.* FROM `hours`
=> []
Why is there one query with WHERE (project_status_id <> 2) and one with WHERE 'projects'.'project_status_id' = 2 ?
Is he trying to subtract the two from each other ?
Any hints how to make this scope work.. ?
thanks
Not sure if this is the most elegant approach, but you can merge relation objects:
scope :intern, lambda { joins(:project).merge(Project.continous) }
Note that I'm wrapping it in a lambda so Project.continuous is not executed when the application loads. Also, this is the preferred way to define scopes in future versions of Rails.