I want to find a record whose title include 'foo' or 'bar'.
For that I wrote like this:
Post.find_by('title like ? or title like ?', '%foo%', '%bar%')
This code works fine, but it's a bit redundant.
If it's a exact match I can use an array. Is there similar way with like condition?
When you pass in a string into an AR query like the above, ( 'title like ? or title like ?', '%foo%', '%bar%' ) you are basically writing a SQL-ish query.
That being said, a way to achieve this is may lie in the use of the similar to keyword in SQL.
This will simplify the query, and remove the repeated use of title like ?.
An example of usage for your scenerio is:
Post.find_by('title similar to ?', '%(foo|bar)%')
Hope this helps.
Related
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)
So let's say i have a Customer model with array column phones.
It's pretty easy to find all customers with given phone
Customer.where('? = ANY(phones)', '+79851234567')
But i can't figure out how to use LIKE with wildcard when i want to find customers with phones similar to given one, something like:
Customer.where('ANY(phones) LIKE ?', '+7985%')
I'm using PostgreSQL 9.5 and Rais 4.2
Any ideas?
I think, first of all, its better to use second table phones with fields customer_id, phone_number. I think it's more rails way ). In this way you can use this query
Phone.where("phone_number LIKE ?", '%PART%').first.customer
If you serialize your array in some text field, by example JSON, you should use % on both sides of your pattern:
Customer.where('phones LIKE ?', '%+7985%')
If you have an array in your database, you should use unnest() function to expand an array to a set of rows.
Can you try this
Customer.where("array_to_string(phones, ', ') like ?", '+7985%')
I believe this will work.
I am using MySql2 in my Rails application. I am trying to make an activerecord query that returns all records that have specified fields containing a string. I have read that downcasing my input and then using lower(column_name) LIKE 'string' should do the trick, but this does not appear to work. My query is exactly as follows:
search = params[:search].to_s.downcase
#current_user.patients.where("lower(last_name) LIKE ? OR lower(first_name) LIKE ? OR lower(identifier) LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%")
Let's say I have a record with the last name "Abbott". When my search param is "Abb" or "bot", this record is returned. However, when the search param is "abb" or "Bot", nothing is returned. It appears that the query is still case sensitive??
I have looked all over and cannot seem to find an answer. I have read multiple times that lower(column_name) should work, but it does not.
Any help is appreciated. Thanks!
I figured it out. It was indeed because the fields were stored as binary values (thanks Vincent). By casting the binary fields as a CHARs, I was able to case-insensitively compare them:
#current_user.patients.where("CAST(last_name AS CHAR) LIKE ? OR ...
I'm experimenting with a few concepts (actually playing and learning by building a RoR version of the 1978 database WHATSIT?).
It basically is a has_many :through structure with Subject -> Tags <- Value. I've tried to replicate a little of the command line structure by using a query text field to enter the commands. Basically things like: What's steve's phone.
Anyhow, with that interface most of the searches use ILIKE. I though about enhancing it by allowing OR conditions using some form of an array. Something like What's steve's [son,daugher]. I got it working by creating the ILIKE clause directly, but not with string replacement.
def bracket_to_ilike(arrel,name,bracket)
bracket_array = bracket.match(/\[([^\]]+)\]/)[1].split(',')
like_clause = bracket_array.map {|i| "#{name} ILiKE '#{i}' "}.join(" OR ")
arrel.where(like_clause)
end
bracket_to_ilike(tags,'tags.name','[son,daughter]') produces the like clause tags.name ILiKE 'son' OR tags.name ILiKE 'daughter'
And it get the relations, but with all the talk about using the form ("tags.name ILiKE ? OR tags.name ? ",v1,v2,vN..)., I though I'd ask if anyone has any ideas on how to do that.
Creating variables on the fly is doable from what I've searched, but not in favor. I just wondered if anyone has tried creating a method that can add a where clause that has a variable number parameters.I tried sending the where clause to the relation, but it didn't like that.
Steve
Couple of things to watch out for in your code...
What will happen when one of the elements of bracket_array contains a single quote?
What will happen if I take it step farther and set an element to say "'; drop tables..."?
My first stab at refactoring your code would be to see if Arel can do it. Or Sequeel, or whatever they call the "metawhere" gem these days. My second stab would be something like this:
arrel.where( [ bracket_array.size.times.map{"#{name} ILIKE ?"}.join(' OR '), *bracket_array ])
I didn't test it, but the idea is to use the size of bracket_array to generate a string of OR'd conditions, then use the splat operator to pass in all the values.
Thanks to Phillip for pointing me in the right direction.
I didn't know you could pass an array to a where clause - that opened up some options
I had used the splat operator a few times, but it didn't hit me that it actually creates an object(variable)
The [son,daughter] stuff was just a console exercise to see what I could do, but not sure what I was going to do with it. I ended up taking the model association and creating the array out of the picture and implemented OR searches.
def array_to_ilike(col_name,keys)
ilike = [keys.map {|i| "#{col_name} ILiKE ? "}.join(" OR "), *keys ]
#ilike = [keys.size.times.map{"#{col_name} ILIKE ?"}.join(' OR '), *keys ]
#both work, guess its just what you are use to.
end
I then allowed a pipe(|) character in my subject,tag,values searches, so a WHATSIT style question
What's Steve's Phone Home|Work => displays home and work phone
steve phone home|work The 's stuff is just for show
steve son|daughter => displays children
phone james%|lori% => displays phone number for anyone who's name starts with james or lori
james%|lori% => dumps all information on anyone who's name starts with james or lori
The query then parses the command and if it encounters a | in any of the words, it will do things like:
t_ilike = array_to_ilike('tags.name',name.split("|"))
# or I actually stored it off on the inital parse
t_ilike = #tuple[:tag][:ilike] ||= ['tags.name ilike ?',tag]
Again this is just a learning exercise in creating a non-CRUD class to deal with the parsing and searching.
Steve
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