Just usuing SQLlite3 can I find the values that are LIKE what I am looking for and pass in an array?
e.g. I'd like something of the sort
Url.where("token LIKE ?" ["_bc","a_b","ab_"])
No. You'll have to repeat the LIKE clauses for each of the condition, something like this:
like_conditions = %w(_bc a_b ab_)
Url.where((["token LIKE ?"] * like_conditions).join(" OR "), *like_conditions)
# => SELECT `urls`.* FROM `urls` WHERE (token like '_bc' OR token like 'a_b' OR token like 'ab_')
The first part of the where clause constructs an array of token LIKE ? strings with the same length as the number of like conditions and joins them using OR (change this to ANDs if you need an ANDed query instead).
The last part just passes the conditions but not as an array but as the individual elements instead (that's what the * does) because the query now has three parameters instead of one.
You could try somehting like this (untested for SQLite):
class Url < ActiveRecord::Base
scope :with_tokens, lambda{|*tokens|
query = tokens.length.times.map{"token LIKE ?"}.join(" OR ")
where(query, *tokens)
}
end
Url.with_tokens("_bc", "a_b", "ab_")
Related
I'm trying to write a query with LIKE and = conditions:
Model.where("extract(day from month) = ? OR lower(name) LIKE ?",params[:filter], "%#{params[:filter]}%".downcase )
I get return for the month value but i don't get any return for the name value.
so how can i use 'LIKE' and 'EQUAL TO' together in one query?
[EDIT] : when i use only the like statement it works fine.
We can definitely use like and equal to in the same query. Your query looks correct. Please check with hardcoded values to double check ex: Model.where("lower(name) LIKE ?", "%aManda%".downcase ) and then same for both conditions together.
I'm attempting to write a Ruby method which accepts an array of strings (for example, ["EG", "K", "C"], and returns all records from a database table where the icao_code field starts with any of those strings (for example, KORD, EGLL, and CYVR would all match). The length of the array will vary, and it will be input by a user, so it needs to be sanitized.
If I were only searching for a single string, I could do something like Airport.where("icao_code LIKE ?", "#{icao_start}%"). However, since I need to search against an arbitrary number of strings, I can't use that syntax.
Right now, I've got it working as follows:
def in_region(icao_starts)
where_clause = icao_starts.map{|i| "icao_code LIKE '#{i}%'"}.join(" OR ")
return Airport.where(where_clause)
end
However, I'm a bit worried using a setup like this with untrusted user input, since I suspect it would be vulnerable to SQL injection.
Is there a better way to get the same result in a more secure way?
You could consider something like this:
def in_region(icao_starts)
where_clause = "icao_code LIKE '#?%' OR " * icao_starts.length
return Airport.where(where_clause.sub(/\ OR\ $/, ''), *icao_starts)
end
This will build up a (potentially very long?) string with ? placeholders. The *icao_starts will expand that array into arguments to the where clause, so each ? will end up getting safely replaced. The sub(/\ OR\ $/, '') simply trims off the final OR (you could append 1=0 instead if you wanted).
If I were you I would also perform a .uniq on icao_starts before you do anything, truncate the array at some sensible upper length limit, and also have a whitelist of permitted values (oh, forget that, I thought users were searching by airport code). That should be pretty much infallible.
You are right about not interpolating user input into your SQL query. This is dangerous and makes your code vulnerable for SQLI attacks.
def in_region(icao_starts)
conditions = icao_starts.map { "icao_code LIKE ?"}
Airport.where(conditions.join(' OR '), *icao_starts.map { |name| "#{name}%"})
end
It is pretty similar than the solution of bogardpd but does not use a Regexp to get rid of the last " OR"
I'm using Rails 5.0.1 and Postgresql as my database. I have a table with column :content which contains words.
The problem: When I'm looking for a specific word, I want to see if the word contains letters (chars) of my choice. Let's say i want to DB to return words containg letters "a", "b" and "c" (all of them, but with no specific order)
What I'm doing: I found that i could use
Word.where("content like ?", "%a%").where("content like ?", "%b%").where("content like ?", "%c%")
OR
Word.where("content like ? content like ? content like ?", "%a%", "%b%", "%c%")
In both cases even if i switch order of given letters/substrings it works fine, ex. both would find word "back", "cab" etc..
The question: Is there any better/more DRY way to do it? What if want to find word with 8 different letters? Do i have to use "content like ?" 8 times? Is it possible to pass arguments as an array? (let's assume i don't know how many letters user will input)
PostgreSQL has a handy expr op all (array) expression so you can say things like:
where content like all (array['%a%', '%b%', '%c'])
as a short form of:
where content like '%a%'
and content like '%b%'
and content like '%c%'
Also, ActiveRecord will conveniently replace a ? placeholder with a comma-delimited list if you hand it a Ruby array. That lets you say things like:
Word.where('content like all (array[?])', %w[a b c].map { |c| "%#{c}%" })
and:
Word.where('content like all (array[?])', some_other_array.map { |c| "%#{c}%" })
I found a solution:
letters = ["%a%", "%b%", "%c%"]
Word.where((['content LIKE ?'] * letters.size).join(' AND '), *letters)
This is easy and much better than I was using.
I think the SIMILAR TO operator might help. It allows you to pass in a regular expression that you could construct on the fly.
letters = ['a', 'b', 'c']
pattern = "%(#{letters.join('|')})%"
Word.where("content SIMILAR TO ?", pattern)
In my app I have invoice numbers like this:
2014.DEV.0001
2014.DEV.0002
2014.TSZ.0003
The three character code is a company code. When a new invoice number needs to be assigned it should look for the last used invoice number for that specific company code and add one to it.
I know the company code, I use a LIKE to search on a partial invoice number like this:
last = Invoice.where("invoice_nr LIKE ?", "#{DateTime.now.year}.#{company_short}.").last
This results in this SQL query:
SELECT "invoices".* FROM "invoices" WHERE "invoices"."account_id" = 1 AND (invoice_nr LIKE '2014.TSZ.') ORDER BY "invoices"."id" DESC LIMIT 1
But unfortunately it doesn't return any results. Any idea to improve this, as searching with LIKE doesn't seem to be correct?
Try wrapping string with % and use lower to convert the query string and result into downcase to avoid any wrong results due to case, try this
last = Invoice.where("lower(invoice_nr) LIKE lower(?)", "%#{DateTime.now.year}.#{company_short}.%").last
You want % for partial match
last = Invoice.where("invoice_nr LIKE ?", "%#{DateTime.now.year}.#{company_short}.%").last
Since you want to match only the left part you need to add one % at the right part of your string
Invoice.where("invoice_nr LIKE ?", "#{DateTime.now.year}.#{company_short}.%").last
I want my code to do two things that is currently not doing
#students = Student.where(["first_name = ? OR middle_name = ? OR last_name = ?", params[:query].split])
Work. (it says im supposed to pass 4 parameters but I want the user to be able to type words and find by those words on each of those fields and return whatever matches)
Actually use Like clause instead of rigid equal clause.
Please Help.
This looks like a problem that would be better suited to using search rather than SQL. Have you considered something like thinking sphinx or act as ferret (solr would probably be overkill).
...if you must do this in sql, you could build a query something like this:
cols = ['first_name', 'last_name', 'middle_name']
query = 'John Smith'
sql_query = cols.map{|c| query.split.map{|q| "#{c} like '?'"}}.join(' OR ')
sql_query_array = query.split * cols.count
Student.where(sql_query, sql_query_array)
I agree with the previous advice that if you need to do search you should look at something like Solr or Sphinx.
Anyhow, this should help you out.
def search
query = params[:query].split.map {|term| "%#{term}%" }
sql = "first_name LIKE ? OR middle_name LIKE ? OR last_name LIKE ?"
#students = Student.where([sql, *query])
end
The answer to step 1 is using Ruby's awesome little feature called the "splat operator" which allows you to take an array and evaluate it as a list of arguments.
The answer to step 2 is to just massage the query string you get back from the params and turn it into something you can use with the LIKE operator. I basically stole this from Railscasts Simple Search Form episode.
I'm not sure if you want all fields to search the same term if that is the case then you can do this:
where("first_name LIKE :term OR middle_name LIKE :term OR last_name LIKE :term", { term: "%#{params[:term]}%"})
No need for any crazy split or map or anything else, this is just straight ActiveRecord