Rails find_by_sql issue - mysql

I have the following call in my RoR script:
Foo.where("event = 'Login'").group('user_id').order('user_id ASC').count()
This gives me a list of all users and how much they have logged in in the form of:
<userid1> => <count>, <userid2> => <count>, ...}
This is great and very close to what I wan but I've been unable to convince it to sort by the count of logins instead, what I'd really like to have it do. There is also a column that has some info about the login session in the form of a character delimited string. I'd like to get at certain parts of that information.
To achieve this I've tried using find_by_sql and when I make the following call:
Foo.find_by_sql("SELECT userid, COUNT(*) AS number, SUBSTRING_INDEX(stuff, ',', 1) AS info FROM <table> WHERE event = 'Login' GROUP BY userid")
What I get is a ilst of Foo entries that contain the userids but not the count or the info. When I run this in the MySQL workbench it works like a charm. Is there something else I need to do to get this to work? Also, would there be a way to just do this using Foo.select or Foo.where? Thanks.
Update I have also tried this format, as demonstrated here:
Foo.find(:all, :select => 'count(*) count, userid', :group =>'userid')
But this too merely responds with the userids and does not spit out the count.
Update 2 Looking at the output a bit more i can see now that when i do the find_by_fql call everything is being found in the correct way and even being sorted. It just isn't actually selecting the COUNT(*) or the SUBSTRING_INDEX.
Update 3 I also tried out this SO tip but when I tell it:
Foo.find(:all, :select => 'userid, count(*) as cnt', :group => 'userid')
It doesn't print or find anything related to the var cnt. I'm totally baffled here because I've seen more than one example now that does it this ^^ way and I've yet to get it to succeed.

Actually, your problem is not an SQL problem. To generate the correct SQL you would just need this:
Foo.where("event = 'Login'").group('user_id').order('count_all').count()
Take a look in your log and you'll find that this generates the following SQL:
SELECT COUNT(*) AS count_all, user_id AS school_id FROM `foos` GROUP BY user_id ORDER BY count_all
...and if you run that in your SQL console you'll get what you want.
The problem is that Rails doesn't return them in this order, Rails always returns these special group/count results in the order of the GROUP BY field. So, if you want them in a different order then you'll need to do it in Ruby after getting the hash back.

Code below returns an array of foos, checking any element inside foo will return userid/cnt
foos = Foo.find(:all, :select => 'userid, count(*) as cnt', :group => 'userid')
Is this what you're looking for?
foos.first.userid # will show userid
foos.first.cnt # will show count

Related

PG::InvalidColumnReference: ERROR when sorting by associated table

I'm trying to sort/order a table (Employer) based on the number of associated records (Employee) each (Employer) has.
The following code works on rails console with the Employer records sorted by number of Employees as intended :
Employer.joins(:employees).group(:id).order('count(employees.id) ASC')
However, when actually trying to run this on development, I get this error:
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
While I could use 'sort_by", I would prefer to have the result in ActiveRecord::Relation format. Any help would be appreciated.
I was able to reproduce this error in the console by adding distinct to the expression:
In your case it would be:
Employer.distinct.joins(:employees).group(:id).order('count(employees.id) ASC')
The error message says that you have to add the expression you sort by (count(employees.id)) to the select list.
That could be achieved by explicitly specifying select columns:
Employer.select('employers.id, count(employees.id)').joins(:employees).group(:id).order('count(employees.id) ASC')

What kind a SQL query use rails to retrieve data in console

When I fire User.first.email in Rails - 4 console it gives me query like
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
By this query structure how it can get email column from database just only firing SELECT "users".* FROM "users" in rails.
Please provide needful description for this.
If you want a query equivalent to SELECT "users".* FROM "users" you should be using the following in your console:
User.all
If you want to just select the email field, generating the query SELECT "users"."email" FROM "users" you would use the pluck modifier, which will return you an array of emails:
User.pluck(:email)
Notice we've dropped the all query modifier. It isn't required in this instance, but you can add it for clarity if you like:
User.all.pluck(:email)
Be aware though that this will return you an array just containing your requested attribute, not instances of the model they belong to.
For that, you would use the select modifier:
User.select(:email)
#or
User.all.select(:email)
This will return you instances of your User class with email populated with the data from the DB. This is useful if you need to chain this query with others, but will be less performant than the pluck alternative with a large dataset.
Try this
#users = User.select("email")

Pass array in raw MySQL query in Ruby on Rails

So, I have a problem. I have a query which returns ids from one table (say table1) and I have to pass those ids to another query which uses table2. (Writing inner selects or joins is not an option due to some certain reasons).
Query:
client = Mysql2::Client.new(:host => "localhost", :username => "", :password => "", :database =>"test")
query1 = %Q{select id from table1 where code='ABC123'}
ids = client.query(query1)
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids}) and status="rejected"}
table2_data = client.query(query2)
ids is Mysql2::Result type
Also, when I do ids.to_a, the resulting array has data something like this: [{"id"=>1}, {"id"=>2}]
I need some feasible way to pass ids to the second query. I tried ids.to_a, but it gives error due to the braces [ ]. I have also tried concatenating, say the MySQL result is:
array = ids.to_a # [1,2,3]
id_new = "("+#{array.join(',')}+")"
id_new becomes "(1,2,3)" which is a string and hence IN doesn't work.
Can anyone please suggest something how to pass ids array in the raw MySQL query?
I have banged my head finding the answer, but couldn't find an appropriate one.
Edit: I can use Active Record only for query1 and if that is the case and ids is an Active Record object, can anyone suggest how to pass it to query2 in the IN clause which is supposed to be a raw SQL query?
Edit2: I can't use Active Record (for query2) or join because it's making the query heavy and taking long time (>10s) to fetch the result (indices are present). So, I am using raw query to optimise it.
When I ran similar queries to try to mimic your problem I saw that I'm getting an array of array for ids, like [["1"], ["2"], ["3"]].
If this is also what you're getting then you should call ids.flatten before calling join:
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids.flatten.join(',')}) and status="rejected"}
array.flatten removes extra braces, so:
[[1], [2], [3]].flatten
# => [1,2,3]
[[1], [2], [3]].flatten.join(',')
# => "1,2,3"
EDIT
Since you reported you are receiving a Mysql2::Result object, do this:
ids.to_a.map(&:values).flatten.join(',')
The to_a first converts the Mysql2::Result to an array of hashes that looks like this:
[{"id"=>"1"}, {"id"=>"2"}]
Then using map(&:values) we convert it to an array that looks like this:
[["1"], ["2"]]
This array is similar to the above (before the edit), so running flatten.join(',') converts it to the string you are looking for.
Note that instead of doing map(&:values).flatten you could use the common shortcut flat_map(&:values) which results in the same thing.
Are you sure it doesn't work because it is a string. I think it doesn't work because of duplicate brackets. Please try this:
array = ids.flat_map(&:values).join(',')
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{array}) and status="rejected"}
I suggest to use a ORM (object-relational mapping) like the ActiveRecord or Sequel gems - especially because building database queries manually by string concatination is error prone and leads to vulnerabilities like sql injections.
If the main reason you posted was to learn how to extract data from an array of hashes, then you can ignore this answer.
However, if you wanted the best way to get the data from the database, I'd suggest you use ActiveRecord to do the donkey work for you:
class Table1 < ActiveRecord::Base
self.table_name = :table1
has_many :table2s
end
class Table2 < ActiveRecord::Base
self.table_name = :table2
belongs_to :table1
end
table2_data = Table2.joins(:table1).where(table1: {code: 'ABC123'}, status: 'rejected')
A key point is that a SQL join, will effectively do the processing of the IDs for you. You could code up the SQL join yourself, but ActiveRecord will do that for you, and allow you to add the additional queries, such that you can gather the data you want in one query.
You can join array with comma, like following code.
ids = ids.to_a.map{|h| h['id']}
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids.join(',')}) and status="rejected"}
table2_data = client.query(query2)
It will work fine.

optionally use table prefix in codeigniter using active records

I want to produce sql like:
select id, "file_name.png" from prefix_table;
In CI, by using active records, I code that with:
$this->db->select('id, "file_name.png"', FALSE)->from('prefix_table');
but what I got is:
select id, prefix_"file_name.png" from prefix_table;
Is there any way to use the table prefix optional? Or may be, how do I do not use the prefix when selecting using active records?
This is a limitation/bug in CodeIgniter right now -- you won't be able to use a string like that without the prefix butting in. I'd suggest writing the query manually. I've opened an issue on Github for it.
Also, you should use an AS column definition when selecting a string like that, otherwise the string name will also be the column name, and you'll end up with something like:
array(
'id' => 2,
'file_name.png' => 'file_name.png'
)

Rails scope overriding select

I have a model with a has_many relationship, and a scope to determine whether or not it has any children, such as:
scope :with_nomination, :include => [:nomination], :conditions => "nominations.service_id IS NOT NULL"
Using this, I can do something like Service.with_nomination and receive a list of all services with nomination children.
The problem is that when I do something like Service.select("id, firstName, lastName").with_nomination ActiveRecord in essense does a SELECT * FROM services which is very bad and does not utilize the indexes I so painstakingly set up.
How can I either rephrase my query or modify my scopes to work with the .select() command?
Turns out in the syntax I was using, a select is not possible, so it does a select * and any further selects are already overriden.
I re-wrote the scopes like so:
scope :no_nomination, joins("LEFT JOIN nominations ON nominations.service_id = services.id").where("nominations.service_id IS NULL")
# important distinction here, the left join allows you to find those records without children
scope :with_nomination, joins(:nomination).where("nominations.service_id IS NOT NULL")
Using this syntax allows me to do something like Service.select(:id,:user,:otherfield).with_nomination
8 years later...
This is ugly, but you could also convert the resulting ActiveRecord::Relation into to sql with to_sql and run the command manually with ActiveRecord::Base.connection.execute.
It might look like this:
query = Service.select("id, firstName, lastName").with_nomination.to_sql
records = ActiveRecord::Base.connection.execute(query)
records.first["firstName"] # => First Name
This doesn't eliminate the excess columns that the scope retrieves, and you have to access each field with string keys, but hey, at least you can still access them!