Use When Case in SQL server to Ruby On Rails - sql

I have this query in SQL
select #cd_x=
Case
when tp_x2='ZZZ' then tp_x3
when tp_x2='XXX' then tp_x3
else
tp_x2
end
from table
where id=#id
How can I translate this query to a sentence in Ruby on Rails?

I think you would be looking at something like:
#cd_x = table.select("CASE WHEN tp_x2='ZZZ' THEN tp_x3 WHEN tp_x2='XXX' then tp_x3 ELSE tp_x2 END").where(:id => id)
I am using this as a model though they created a whole module out of it rather than one line: Case Statement in ActiveRecord

I had a kind a similar problem. I wanted to update more records with one query. Basically an order sorting update, using CASE sql.
#List of product ids in sorted order. Get from jqueryui sortable plugin.
#product_ids = [3,1,2,4,7,6,5]
#product_ids.each_with_index do |id, index|
# Product.where(id: id).update_all(sort_order: index+1)
#end
##CASE syntax example:
##Product.where(id: product_ids).update_all("sort_order = CASE id WHEN 539 THEN 1 WHEN 540 THEN 2 WHEN 542 THEN 3 END")
case_string = "sort_order = CASE id "
product_ids.each_with_index do |id, index|
case_string += "WHEN #{id} THEN #{index+1} "
end
case_string += "END"
Product.where(id: product_ids).update_all(case_string)

Related

SQL injections in Rails 4 issue

I'm trying to learn about SQL injections and have tried to implement these, but when I put this code in my controller:
params[:username] = "johndoe') OR admin = 't' --"
#user_query = User.find(:first, :conditions => "username = '#{params[:username]}'")
I get the following error:
Couldn't find all Users with 'id': (first, {:conditions=>"username = 'johndoe') OR admin = 't' --'"}) (found 0 results, but was looking for 2)
I have created a User Model with the username "johndoe", but I am still getting no proper response. BTW I am using Rails 4.
You're using an ancient Rails syntax. Don't use
find(:first, :condition => <condition>) ...
Instead use
User.where(<condtion>).first
find accepts a list of IDs to lookup records for. You're giving it an ID of :first and an ID of condition: ..., which aren't going to match any records.
User.where(attr1: value, attr2: value2)
or for single items
User.find_by(attr1: value, attr2: value)
Bear in mind that while doing all this, it would be valuable to check what the actual sql statement is by adding "to_sql" to the end of the query method (From what I remember, find_by just does a LIMIT by 1)

Protecting against sql injection using activerecord

Following on the question how can I use like query in ruby with sinatra? I have the following problem securing my sql from injection.Here is my method to make a query from the type string, it receives a v(alue) to search for and a k(ey) (=field) to look in.
After that the various selctions are joined by selection.join(' and ')
def string_selector(k, v)
case
when v[/\|/]
v.scan(/([^\|]+)(\|)([^\|]+)/).map {|p| "lower(#{k}) LIKE '%#{p.first.downcase}%' or lower(#{k}) LIKE '%#{p.last.downcase}%'"}
when v[/[<>=]/]
v.scan(/(<=?|>=?|=)([^<>=]+)/).map { |part| p part; "#{k} #{part.first} '#{part.last.strip}'"}
else
# "lower(#{k}) LIKE '%#{v.downcase}%'" #(works)
("lower(#{k}) LIKE ?", '%#{v.downcase}%') #doesn't work
end
end
But i get the error
selectors.rb:38: syntax error, unexpected keyword_end, expecting $end
from C:/../1.9.1/rubygems/core_ext/kernel_require.rb:55:in `require'
What could i be doing wrong ?
There's got to be a better way to do what you are trying to do if you are using ActiveRecord... However, if you need to support your string_selector functionality for some reason, I would at least use Arel:
def string_selector(k, v)
tbl = Arel::Table.new(:test) # your table, or you could pass this in...
condition = case v
when /\|/
vals = v.split(/\|/)
first = vals.shift
vals.inject(tbl[k].matches("%#{first.strip}%")) do |acc, val|
acc.or(tbl[k].matches("%#{val.strip}%"))
end
when /<>/
tbl[k].not_eq(v.gsub(/<>/, '').strip)
when /\=/
tbl[k].eq(v.gsub(/\=/, '').strip)
else
tbl[k].matches(v.strip)
end
tbl.where(condition).to_sql
end
Please note that matches will perform a case insensitive query for you (e.g., by using ILIKE in PostgreSQL).

How to Write Where Clause Condition in Rails using AND & OR Operator

def self.get_previous_feedback current_feedback
Feedback.where("feedbacks.id < ?", current_feedback.id).order('created_at asc').last
end
def self.get_next_feedback current_feedback
Feedback.where("feedbacks.id > ?", current_feedback.id).order('created_at asc').first
end
#current_feeedback is the show page of any feedback.( feedback/show/id=2)
I have got 3 tables in my DB. Feedback, User, Department are connected in one-many relation.
By running above codes I am able to navigate to next/previous Feedback.
My User (current_user) is logged in, and Now on clicking prev/next, I want to retrieve the next feedback from DB(where condition written above) + whose feedback.department_id = current_user.deparment_id.
For including department_id in need to write an AND statement. How to do that ?
Try this...
def self.get_previous_feedback(current_feedback,current_user)
Feedback.where("id < ? & department_id = ?", current_feedback.id, current_user.department_id).order('created_at asc').last
end
def self.get_next_feedback(current_feedback,current_user)
Feedback.where("id > ? & department_id = ?", current_feedback.id, current_user.department_id).order('created_at asc').first
end
Thanks for pointing me out the correct logic.
Above query needs bit modification to work correctly.
simply id does not link to the feedbacks table, hence had to use feedbacks.id
& needs to be replaced by AND.
Corrected Code :
Feedback.where("feedbacks.id < ? AND feedbacks.department_id = ?",
current_feedback.id, current_user.department_id).order('created_at
asc').last
Thanks Man ! :)
You can also chain the two conditions, that way you don't need use the AND and you can also re-use the department_id clause.
def self.get_department_feedback current_user
Feedback.where(department_id: current_user.deparment_id)
end
def self.get_previous_feedback(current_feedback,current_user)
get_deparment_feedback(current_user).where("feedbacks.id < ?", current_feedback.id).order('created_at asc').last
end
def self.get_next_feedback(current_feedback,current_user)
get_deparment_feedback(current_user).where("feedbacks.id > ?", current_feedback.id).order('created_at asc').first
end

Efficient way to update multiple records with independent values?

I have the following (overly db expensive) method:
def reorder_area_routes_by_demographics!
self.area_routes.joins(:route).order(self.demo_criteria, :proximity_rank).readonly(false).each_with_index do |area_route, i|
area_route.update_attributes(match_rank: i)
end
end
But this results in an UPDATE query for each area_route. Is there a way to do this in one query?
--Edit--
Final solution, per coreyward suggestion:
def reorder_area_routes_by_demographics!
sorted_ids = area_routes.joins(:route).order(self.demo_criteria, :proximity_rank).pluck(:'area_routes.id')
AreaRoute.update_all [efficient_sort_sql(sorted_ids), *sorted_ids], {id: sorted_ids}
end
def efficient_sort_sql(sorted_ids, offset=0)
offset.upto(offset + sorted_ids.count - 1).inject('match_rank = CASE id ') do |sql, i|
sql << "WHEN ? THEN #{id} "
end << 'END'
end
I use the following to do a similar task: updating the sort positions of a bevy of records according to their order in params. You might need to refactor or incorporate this differently to accomodate the scopes you're applying, but I think this will send you in the right direction.
def efficient_sort_sql(sortable_ids, offset = 1)
offset.upto(offset + sortable_ids.count - 1).reduce('position = CASE id ') do |sql, i|
sql << "WHEN ? THEN #{i} "
end << 'END'
end
Model.update_all [efficient_sort_sql(sortable_ids, offset), *sortable_ids], { id: sortable_ids }
sortable_ids is an array of integers representing the ids of each object. The resulting SQL looks something like this:
UPDATE pancakes SET position = CASE id WHEN 5 THEN 1 WHEN 3 THEN 2 WHEN 4 THEN 3 WHEN 1 THEN 4 WHEN 2 THEN 5 WHERE id IN (5,3,4,1,2);
This is, ugliness aside, a pretty performant query and (at least in Postgresql) will either fully succeed or fully fail.

django using .extra() got error `only a single result allowed for a SELECT that is part of an expression`

I'm trying to use .extra() where the query return more than 1 result, like :
'SELECT "books_books"."*" FROM "books_books" WHERE "books_books"."owner_id" = %s' % request.user.id
I got an error : only a single result allowed for a SELECT that is part of an expression
Try it on dev-server using sqlite3. Anybody knows how to fix this? Or my query is wrong?
EDIT:
I'm using django-simple-ratings, my model like this :
class Thread(models.Model):
#
#
ratings = Ratings()
I want to display each Thread's ratings and whether a user already rated it or not. For 2 items, it will hit 6 times, 1 for the actual Thread and 2 for accessing the ratings. The query:
threads = Thread.ratings.order_by_rating().filter(section = section)\
.select_related('creator')\
.prefetch_related('replies')
threads = threads.extra(select = dict(myratings = "SELECT SUM('section_threadrating'.'score') AS 'agg' FROM 'section_threadrating' WHERE 'section_threadrating'.'content_object_id' = 'section_thread'.'id' ",)
Then i can print each Thread's ratings without hitting the db more. For the 2nd query, i add :
#continue from extra
blahblah.extra(select = dict(myratings = '#####code above####',
voter_id = "SELECT 'section_threadrating'.'user_id' FROM 'section_threadrating' WHERE ('section_threadrating'.'content_object_id' = 'section_thread'.'id' AND 'section_threadrating'.'user_id' = '3') "))
Hard-coded the user_id. Then when i use it on template like this :
{% ifequal threads.voter_id user.id %}
#the rest of the code
I got an error : only a single result allowed for a SELECT that is part of an expression
Let me know if it's not clear enough.
The problem is in the query. Generally, when you are writing subqueries, they must return only 1 result. So a subquery like the one voter_id:
select ..., (select sectio_threadrating.user_id from ...) as voter_id from ....
is invalid, because it can return more than one result. If you are sure it will always return one result, you can use the max() or min() aggregation function:
blahblah.extra(select = dict(myratings = '#####code above####',
voter_id = "SELECT max('section_threadrating'.'user_id') FROM 'section_threadrating' WHERE ('section_threadrating'.'content_object_id' = 'section_thread'.'id' AND 'section_threadrating'.'user_id' = '3') "))
This will make the subquery always return 1 result.
Removing that hard-code, what user_id are you expecting to retrieve here? Maybe you just can't reduce to 1 user using only SQL.