Rails Relation with Duplicate Column Names - ruby-on-rails

I have a typical set of models with a parent/child/grandchild/ggrand hierarchy. I create the relation with:
#summary = Function.joins(:benchmrks=>{:indicators=>:results})
.select("functions.id , benchmrks.id , indicators.id , avg(results.score) ")
.group("results.indicator_id")
I had trouble accessing columns in my result set so I did this:
#summary.each do |s|
s.attributes
end
and I see two columns defined and they are both accessible:
{"id"=>1, "avg(results.score)"=>3.0}
if I change my .select() and manually rename the columns to:
.select("functions.id as f_id, benchmrks.id as b_id, indicators.id as i_id , avg(results.score) as avg")
I see 4 columns and they are all accessible:
{"f_id"=>1, "b_id"=>1, "i_id"=>1, "avg"=>3.0}
so the dot SQL syntax of column names (functions.id, benchmrks.id, and indicators_id) seem to get collapsed into a single id by rails. This was unexpected behavior to me.
Should I have done this a different way?

Related

Get Rapidminer to transpose/pivot a single attribute/column in a table

I have a table that looks like the following:
ID City Code
"1005AE" "Oakland" "Value1"
"1006BR" "St.Louis" "Value2"
"102AC" "Miami" "Value1"
"103AE" "Denver" "Value3"
And I want to transpose/pivot the Code examples/values into column attributes like this:
ID City Value1 Value2 Value3
"1005" "Oakland" 1 0 0
"1006" "St.Louis" 0 1 0
"1012" "Miami" 1 0 0
"1030" "Denver" 0 0 1
Note that the ID field is numeric values encoded as strings because Rapidminer had trouble importing bigint datatypes. So that is a separate issue I need to fix--but my focus here is the pivoting or transposing of the data.
I read through a few different Stackoverflow posts listed below. They suggested the Pivot or Transpose operations. I tried both of these, but for some reason I am getting either a huge table which creates City as a dummy variable as well, or just some subset of attribute columns.
How can I set the rows to be the attributes and columns the samples in rapidminer?
Rapidminer data transpose equivalent to melt in R
Any suggestions would be appreciated.
In pivoting, the group attribute parameter dictates how many rows there will be and the index attribute parameter dictates what the last part of the name of new attributes will be. The first part of the name of each new attribute is driven by any other regular attributes that are neither group nor index and the value within the cell is the value found in the original example set.
This means you have to create a new attribute with a constant value of 1; use Generate Attributes for this. Set the role of the ID attribute to be ID so that it is no longer a regular attribute; use Set Role for this. In the Pivot operator, set the group attribute to be City and the index attribute to be Code. The end result is close to what you want. The final steps are, firstly to set missing values to be 0; use Replace Missing Values for this and, secondly to rename the attributes to match what you want; use Rename for this.
You will have to join the result back to the original since the pivot operation loses the ID.
You can find a worked example here http://rapidminernotes.blogspot.co.uk/2011/05/worked-example-using-pivot-operator.html

GroupingError: ERROR: column must appear in the GROUP BY clause or be used in an aggregate function

I have code in my controller that is ranking albums by the highest average review rating (used code from this solution How to display highest rated albums through a has_many reviews relationship):
#albums = Album.joins(:reviews).select("*, avg(reviews.rating) as average_rating").group("albums.id").order("average_rating DESC")
This code works perfectly in my development environment (sqlite3), however when I pushed the code to heroku and to postgresql I got this error:
PG::GroupingError: ERROR: column "reviews.id" must appear in the GROUP BY clause or be used in an aggregate function
I realize this is a fairly common problem, I am a bit inexperienced with SQL so I am having trouble refactoring the code so it will work in both my development and production environments.
You are not allowed to select reviews.id (selected implicitly through the wildcard *) without adding it to the GROUP BY clause or applying an aggregate function like avg(). The solution is to do one of the following:
Remove the wildcard * from your select
Add the field reviews.id to your group clause
Select reviews.id explicitly and apply an aggregate function to it (e.g. sum(reviews.id))
Replace the wildcard * with the table-specific wildcard albums.*
The second and third option do not make much sense in your scenario though.
Based on your comment, I added option four.
Just would like to share this code on ruby using active record (sinatra)
I had to add "group by" to an "order by" function, so line of code ...
from:
#models = Port.all.order('number asc')
to:
#models = Port.select(:id, :device_id, :number, :value, :sensor, :UOM).all.order('number asc').group(:id,:sensor,:UOM)
and it worked perfect, just remember the ID field in this case "Port.id" must be added to the group clause otherwise will raise this error, and as #slash mentioned you can not achieve this with special functions (select implicitly through the wildcard * or in my case using "all")

Rails: how to split string after a specific character

I have a column name in database which holds strings like CVM™ what I want to do is to split it so everything after ampersand goes into a different column and everything before the string stays where it was. The final result should put ™ into a column called abbr and save CVM into name column.
Create a rake task file
lib/tasks/split_name.rake
Then paste in the following, and change "TableName" to your actual table name.
task :split_name => :environment do
TableName.all.each do |r|
a = r.name.split("&") #assuming exact same string format, and not null
r.update_attribute(:name, a[0])
r.update_attribute(:abbr, '&' + a[1])
end
end
Then run it as such
rake split_name
Must this be done Rails level? You can also do this with just Postgres with split_part function. I assume you're working with users table:
ALTER TABLE users ADD COLUMN abbr VARCHAR;
UPDATE users
SET name = SPLIT_PART(name, '™', 1),
abbr = '™' || SPLIT_PART(name, '™', 2);
You can also bake these queries into a Rake task if needed.
Update: Opps I assumed you were using Postgres. But other languages should have similar method that you can use.

Modifying the returned value of find_by_sql

So I am pulling my hair over this issue / gotcha. Basically I used find_by_sql to fetch data from my database. I did this because the query has lots of columns and table joins and I think using ActiveRecord and associations will slow it down.
I managed to pull the data and now I wanted to modify returned values. I did this by looping through the result ,for example.
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
end
What I found out is that project['mycolumn'] was not changed at all.
So my question:
Does find_by_sql return an array Hashes?
Is it possible to modify the value of one of the attributes of hash as stated above?
Here is the code : http://pastie.org/4213454 . If you can have a look at summarize_roles2() that's where the action is taking place.
Thank you. Im using Rails 2.1.1 and Ruby 1.8. I can't really upgrade because of legacy codes.
Just change the method above to access the values, print value of project and you can clearly check the object property.
The results will be returned as an array with columns requested encapsulated as attributes of the model you call this method from.If you call Product.find_by_sql then the results will be returned in a Product object with the attributes you specified in the SQL query.
If you call a complicated SQL query which spans multiple tables the columns specified by the SELECT will be attributes of the model, whether or not they are columns of the corresponding table.
Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
> [#<Post:0x36bff9c #attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
Source: http://api.rubyonrails.org/v2.3.8/
Have you tried
a = Project.find_by_sql("SELECT mycolumn, mycolumn2 FROM my_table").each do |project|
project['mycolumn'] = project['mycolumn'].split('_').first
project.save
end

ActiveRecord find and only return selected columns

edit 2
If you stumble across this, check both answers as I'd now use pluck for this
I have a fairly large custom dataset that I'd like to return to be echoe'd out as json. One part is:
l=Location.find(row.id)
tmp[row.id]=l
but I'd like to do something like:
l=Location.find(row.id).select("name, website, city")
tmp[row.id]=l
but this doesn't seem to be working. How would I get this to work?
thx
edit 1
alternatively, is there a way that I can pass an array of only the attributes I want included?
pluck(column_name)
This method is designed to perform select by a single column as direct SQL query Returns Array with values of the specified column name The values has same data type as column.
Examples:
Person.pluck(:id) # SELECT people.id FROM people
Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
Person.where(:confirmed => true).limit(5).pluck(:id)
see http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-pluck
Its introduced rails 3.2 onwards and accepts only single column. In rails 4, it accepts multiple columns
In Rails 2
l = Location.find(:id => id, :select => "name, website, city", :limit => 1)
...or...
l = Location.find_by_sql(:conditions => ["SELECT name, website, city FROM locations WHERE id = ? LIMIT 1", id])
This reference doc gives you the entire list of options you can use with .find, including how to limit by number, id, or any other arbitrary column/constraint.
In Rails 3 w/ActiveRecord Query Interface
l = Location.where(["id = ?", id]).select("name, website, city").first
Ref: Active Record Query Interface
You can also swap the order of these chained calls, doing .select(...).where(...).first - all these calls do is construct the SQL query and then send it off.
My answer comes quite late because I'm a pretty new developer. This is what you can do:
Location.select(:name, :website, :city).find(row.id)
Btw, this is Rails 4

Resources