So I am attempting to do a SQL query where I need to provide a lot of user id's.
Currently what I am trying to do is this:
following_ids = $redis.smembers(self.redis_key(:following))
Medium.includes([{comments: :user}, :likes, :user]).where("user_id IN (#{ following_ids.each { |id| id } }) OR user_id = :user_id", user_id: id)
Now the following_ids is in this form: ["1", "2", "3"], containing user id's
But the loop I try to do seems to still put the whole array into the SQL statement. I can see this from my server log:
Medium Load (0.3ms) SELECT "media".* FROM "media" WHERE (user_id IN (["2", "3"]) OR user_id = 1)
PG::SyntaxError: ERROR: syntax error at or near "["
LINE 1: SELECT "media".* FROM "media" WHERE (user_id IN (["2", "3"])...
What am I doing wrong here?
You don't need to complicate things that much. You could simply do
user_ids = $redis.smembers(self.redis_key(:following)) << id
Medium.includes([{comments: :user}, :likes, :user]).where(user_id: user_ids)
Try changing the interpolated code to:
following_ids.to_s.tr("[]", "")
So in total:
Medium.includes([{comments: :user}, :likes, :user]).where("user_id IN ( #{following_ids.to_s.tr("[]", "") }) OR user_id = :user_id", user_id: id)
UPDATE: See Kaspar's comment below for the preferred 'active record way' to do this.
Related
I need to get some data from ActiveRecord, I have following two tables Department and users I have issue that I am getting one hash in which user is giving me user_ids and emails, now I want to create hash container users, departments and emails in specific format. I have tried a lot map/select but still could not figure out any simple way.
class User < ApplicationRecord
belongs_to :department
end
And Department
class Department < ApplicationRecord
has_many :users
end
I am getting following values from user
sample_params = [{ user_id: 1, email: 'example1#example.com' },
{ user_id: 5, email: 'example5#example.com' },
{ user_id: 13, email: 'example13#example.com'}]
Now I have retrieve departments from database and other data and join in one huge hash so I can add it to my class. I can get all my users by following command
users = User.where(id: sample_params.map{ |m| m[:user_id] })
I will get whole ActiveRecord objects with all users if I run following command I am getting all user_id and project_id
users.map { |u| {user_id: u.id, department_id: u.department_id } }
I will get this
[{:user_id=>1, :department_id=>3},
{:user_id=>5, :department_id=>3},
{:user_id=>13, :department_id=>2}]
but I want to create following hash, Is there any simple way to do it directly using query or in other few lines, I can try using Iteration but that would be very long and complicated. As I also need to merge emails in it and add one project instead of same projects multiple ids.
[
{department_id: 1, users: [{user_id: 1, email: 'example1#example.com'},
{user_id: 5, email: 'example5#example.com'}]},
{department_id: 2, users: [{ user_id: 13, email: 'example13#example.com']
I am using here sample data the real data is very very large include hundreds of users and many departments.
I don't think you can do it in one go! let us run throgh and try how can we solve it. First instead of using map in your params let us try another alternate for it. Remove following line
users = User.where(id: sample_params.map{ |m| m[:user_id] })
user following line
User.where(id: sample_params.pluck(:user_id)).pluck(:department_id, :id).grou
p_by(&:first)
This will bring you result in each users id with department grouping in one query
{3=>[[3, 1], [3, 5]], 2=>[[2, 13]]}
Now we are getting department_id and user_id so we will run map on them to get first array with department_id and user_id in a group with following command
data =
User
.where(id: sample_params.pluck(:user_id))
.pluck(:department_id, :id)
.group_by(&:first).map do |department_id, users|
{ department_id: department_id,
users: users.map { |_, id| id } }
end
This will give you hash with department and users
[{:department_id=>3, :users=>[1, 5]}, {:department_id=>2, :users=>[13]}]
Now you have hash of department and users. so let us work on this this is second phase where I will use select to get email from your params and merge it to your data.
result = []
data.each do |department|
department_users = []
department[:users].each do |user|
emails = sample_params.select { |user| user[:user_id] == 1 }[0][:email];
department_users << { id: user, emails: emails }
end; result << {department_id: department[:department_id], users: department_users}
end
Now if you do puts result[0] you will get following hash as you wanted.
{:department_id=>3, :users=>[{:id=>1, :emails=>"example1#example.com"}, {:id=>5, :emails=>"example1#example.com"}]}, {:department_id=>2, :users=>[{:id=>13, :emails=>"example1#example.com"}]}
This will solve issue, I understand there are two operation but there is no double sql queries in single it is working and you are also replacing your emails. I hope it solve you issue, any one can make it simpler would be appreciated.
I have a jsonb column in my postgres performances table called authorization where I store the uuid of a user as a key and their authorization level as the value e.g.
{ 'sf4wfw4fw4fwf4f': 'owner', 'ujdtud5vd9': 'editor' }
I use the below Rails query in my Performance model to search for all records where the user is an owner:
class Performance < ApplicationRecord
def self.performing_or_owned_by(account)
left_outer_joins(:artists)
.where(artists: { id: account } )
.or(Performance.left_outer_joins(:artists)
# this is where the error happens
.where("authorization #> ?", { account => "owner" }.to_json)
).order('lower(duration) DESC')
.uniq
end
end
Where account is the account uuid of the user. However, when I run the query I get the following error:
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "#>")
LINE 1: ..._id" WHERE ("artists"."id" = $1 OR (authorization #> '{"28b5...
The generated SQL is:
SELECT "performances".* FROM "performances"
LEFT OUTER JOIN "artist_performances" ON "artist_performances"."performance_id" = "performances"."id"
LEFT OUTER JOIN "artists" ON "artists"."id" = "artist_performances"."artist_id" WHERE ("artists"."id" = $1 OR (authorization #> '{"28b5fc7f-3a31-473e-93d4-b36f3b913269":"owner"}'))
ORDER BY lower(duration) DESC
I tried several things but keep getting the same error. Where am I going wrong?
The solution as per comment in the original question is to wrap the authorization in double-quotes. Eg:
.where('"authorization" #> ?', { account => "owner" }.to_json)
The ->> operator gets a JSON object field as text.
So it looks you need this query:
left_outer_joins(:artists).
where("artists.id = ? OR authorization ->> ? = 'owner'", account, account).
order('lower(duration) DESC').
uniq
I have a table called Student, the student table contains the id, first_name and last_name. I am trying to select and concatenate first_name and last_name and display the column as "Name". This is my query:
Student.select("concat(first_name, ' ', last_name) as 'Name'").find(201410204)
but it returns
SELECT concat(first_name, ' ', last_name) as 'Name' FROM `students` WHERE `students`.`id` = 201410204 LIMIT 1
#<Student id: nil>
but when i try to paste the query inside mysql workbench it return the student name that has the id 201410204 and the column name is "Name".
Click to see the result of the query
What's the problem about my code? Thanks
You need to select the id as well:
Student.select(:id, "CONCAT(first_name,' ',last_name) as name").find(201410204)
2.4.1 :073 > Student.select(:id, "CONCAT(first_name,' ',last_name) as name").find(23000)
Student Load (0.9ms) SELECT "students"."id", CONCAT(first_name,' ',last_name) as name FROM "students" WHERE "students"."id" = $1 LIMIT $2 [["id", 23000], ["LIMIT", 1]]
=> #<Student id: 23000, name: "Joe Blow">
Let me give you two solutions, one by using the power of rails and other using the scalability of rails.
1) (Using power of rails)
In the Student model create a method named full_name and concatenate first_name and last_name.
class Student < ActiveRecord::Base
.....
def full_name
"#{try(:first_name)} #{try(:last_name)}".to_s
end
.....
end
Now open console or any where you need,
student = Student.find(201410204)
student.full_name
thats it, you will get the full name.
2) Using Scalability of rails, even can execute SQL queries.
student = ActiveRecord::Base.connection.exec_query("SELECT CONCAT(first_name,' ', last_name) FROM students where students.id = 201410204")
It returns an array,now you can retrieve using,
student.rows[0]
Thats it, you will get the same result.
One solution
In model Create A method
def full_name
"#{self.try(:first_name)} #{self.try(:last_name)}"
end
Now
Student.find(121).full_name
With select you restrict the returned columns. Your query returns only the Column Name. id and all other columns are missing. In this case you get an instance of student where all attributes are nil. Only the attribute Name is set. When you try Student.select("concat(first_name, ' ', last_name) as 'Name'").find(201410204).Name you will see the right result.
When you want to get all attributes you have to extend your select like this Student.select("concat(first_name, ' ', last_name) as 'Name', *").find(201410204)
Edit:
I did check it for mysql on https://de.piliapp.com/mysql-syntax-check/, I don't know why but you have to name * first.
Student.select("*, concat(first_name, ' ', last_name) as 'Name'").find(201410204)
I would like to write a class function for my model that returns one random record that meets my condition and excludes some records. The idea is that I will make a "random articles section."
I would like my function to look like this
Article.randomArticle([1, 5, 10]) # array of article ids to exclude
Some pseudo code:
ids_to_exclude = [1,2,3]
loop do
returned_article = Article.where(published: true).sample
break unless ids_to_exclude.include?(returned_article.id)
do
Lets look at DB specific option.
class Article
# ...
def self.random(limit: 10)
scope = Article.where(published: true)
# postgres, sqlite
scope.limit(limit).order('RANDOM()')
# mysql
scope.limit(limit).order('RAND()')
end
end
Article.random asks the database to get 10 random records for us.
So lets look at how we would add an option to exclude some records:
class Article
# ...
def self.random(limit: 10, except: nil)
scope = Article.where(published: true)
if except
scope = scope.where.not(id: except)
end
scope.limit(limit).order('RANDOM()')
end
end
Now Article.random(except: [1,2,3]) would get 10 records where the id is not [1,2,3].
This is because .where in rails returns a scope which is chain-able. For example:
> User.where(email: 'test#example.com').where.not(id: 1)
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) [["email", "test#example.com"], ["id", 1]]
=> #<ActiveRecord::Relation []>
We could even pass a scope here:
# cause everyone hates Bob
Article.random( except: Article.where(author: 'Bob') )
See Rails Quick Tips - Random Records for why a DB specific solution is a good choice here.
You can use some like this:
ids_to_exclude = [1,2,3,4]
Article.where("published = ? AND id NOT IN (?)", true , ids_to_exclude ).order( "RANDOM()" ).first
I need to display a UI element (e.g. a star or checkmark) for employees that are 'favorites' of the current user (another employee).
The Employee model has the following relationship defined to support this:
has_and_belongs_to_many :favorites, :class_name => "Employee", :join_table => "favorites",
:association_foreign_key => "favorite_id", :foreign_key => "employee_id"
The favorites has two fields: employee_id, favorite_id.
If I were to write SQL, the following query would give me the results that I want:
SELECT id, account,
IF(
(
SELECT favorite_id
FROM favorites
WHERE favorite_id=p.id
AND employee_id = ?
) IS NULL, FALSE, TRUE) isFavorite
FROM employees
Where the '?' would be replaced by the session[:user_id].
How do I represent the isFavorite scalar query in Rails?
Another approach would use a query like this:
SELECT id, account, IF(favorite_id IS NULL, FALSE, TRUE) isFavorite
FROM employees e
LEFT OUTER JOIN favorites f ON e.id=f.favorite_id
AND employee_id = ?
Again, the '?' is replaced by the session[:user_id] value.
I've had some success writing this in Rails:
ee=Employee.find(:all, :joins=>"LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=1", :select=>"employees.*,favorites.favorite_id")
Unfortunately, when I try to make this query 'dynamic' by replacing the '1' with a '?', I get errors.
ee=Employee.find(:all, :joins=>["LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=?",1], :select=>"employees.*,favorites.favorite_id")
Obviously, I have the syntax wrong, but can :joins expressions be 'dynamic'? Is this a case for a Lambda expression?
I do hope to add other filters to this query and use it with will_paginate and acts_as_taggable_on, if that makes a difference.
edit
errors from trying to make :joins dynamic:
ActiveRecord::ConfigurationError: Association named 'LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=?' was not found; perhaps you misspelled it?
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1906:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1911:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1910:in `each'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1910:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1830:in `initialize'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1789:in `new'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1789:in `add_joins!'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1686:in `construct_finder_sql'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1548:in `find_every'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:615:in `find'
Try this:
ee=Employee.all(
:select=>"employees.*,favorites.favorite_id",
:joins=>"LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = #{session[:user_id]}")
Or to be exact:
joins = Employee.send(:sanitize_sql_array,
["LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = ? ", session[:user_id]
])
ee=Employee.find(:all,
:select=>"employees.*,favorites.favorite_id",
:joins=> joins )
Second approach addresses the SQL injection issues.
Edit 1
To test these calls in irb do the following:
Simulate the session object by creating hash:
>> session = {:user_id => "1" }
session = {:user_id => "1" }
=> {:user_id=>"1"}
Execute the finder:
>> ee=Employee.find(:all,
:select=>"employees.*,favorites.favorite_id",
:joins=>"LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = #{session[:user_id]}")
I imagine both ways are possible, but normally, I'd stick the condition in the WHERE clause (:conditions):
ee = Employee.find(:all,
:select => 'employees.*, favorites.favorite_id',
:conditions => ['favorites.employee_id = ?', 1],
:joins => 'LEFT OUTER JOIN favorites ON employees.id = favorites.favorite_id'
)
:joins expects either a raw string or a symbol (association name), or an array of associations. So you can't have dynamic conditions there.
See parameters section here.