My sql in rails
#search = #search.where('txt_1 OR txt_2 OR keywords like ?', some_value)
Why this doesn't work. When I have only " txt_1 OR txt_2 " - i think it works ok. But when I add next OR, there is no result when some_value doesn't exist in last OR.
ANSWER:
ok as I see, OR is condition that return true for one OR/AND second variable. I make this like that, and it works - I don't know is this a good solution
#items.where(' (txt_1 OR txt_2 like ?) OR (keywords like ?) ', "%#{search_name}%","%#{search_name}%")
The OR statement in SQL has to be used to compare different assertion, like something = 'something' OR other_thing = 'other_stuff'.
In your case, you should use the OR keyword as the following:
#items.where('txt_1 LIKE ? OR txt_2 LIKE ? OR keywords LIKE ?', "%#{search_name}%","%#{search_name}%", "%#{search_name}%")
But since you use the same value (search_name), you can use this pretty usefull syntax:
#items.where('txt_1 LIKE :search OR txt_2 LIKE :search OR keywords LIKE :search', search: "%#{search_name}%")
Related
I am trying to create a dynamic query based on User inputs
if a
conditions << ["fname like (?)"]
sql_params << "abc"
end
if b
conditions << ["lname like (?)"]
sql_params << "def"
end
if c
cconditions << ["middle like (?)"]
sql_params << "xyz"
end
results = Model.where(conditions.join(' AND '), sql_params)
While the conditions get in correct syntax, the sql_params are listed as an array. This forms the below query
Model.where("fname like (?) AND lname like (?) AND middle like (?)", ["abc","def","xyz"])
while what I need is
Model.where("fname like (?) AND lname like (?) AND middle like (?)", "abc","def","xyz")
I tried several map/join etc options on the sql_params array but nothing worked.
Are you sure you need to produce exactly this?
Model.where("fname like (?) AND lname like (?) AND middle like (?)", "abc","def","xyz")
Something of the form:
Model.where('expr1 AND expr2 AND expr3')
is equivalent to:
Model.where('expr1').where('expr2').where('expr3')
so why not build the query piece by piece rather than messing around with strings? Something like this:
query = Model.all
query = query.where('fname like ?', 'abc') if a
query = query.where('lname like ?', 'def') if b
query = query.where('middle like ?', 'xyz') if c
will give you the same result.
You need to pass sql_params with * (known as splat operator) i.e.
results = Model.where(conditions.join(' AND '), *sql_params)
How can I perform a query with multiple LIKE parameters?
For example, I have this string to search through:
"I like searching very much"
This is the code I currently use:
$searTerm = "like"
$this->db->or_like('list.description', $SearchTerm,'both');
But i want to search with 2 or 3 parameters. like this:
$searTerm = "like"
$searTerm1 = "much"
How can i perform this to get the same result?
You can simply repeat the like parameters on the active record. In your example you would do something like this:
$this->db->or_like('list.description', $searchTerm1);
$this->db->or_like('list.description', $searchTerm2);
$this->db->or_like('list.description', $searchTerm3);
...
This will just join each or_like with an AND in the WHERE clause.
Firstly, you need to define the array with like variables then, its very important to put the or_like statement above the where clause in order to make multiple 'OR' statements for like 'AND' the where clause.
Here is example:
$this->db->or_like(array('column_name1' => $k, 'column_name2' => $k))
$this->db->where($whereColumn, $whereValue)
You can use like group
$this->db->group_start()->like('column_name1', $value)
->or_group_start()
->like('column_name2', $value)
->group_end()
->group_end();
I have a DQL query like:
$em->createQuery("
SELECT r
FROM WeAdminBundle:FamilyRelation r
WHERE r.col like :query
")
Now I want to change "col" depending on various parameters. How can i achieve this with DQL since the normal setParameter doesn't work here.
You can use setParameter with DQL, as many examples are provided but for LIKE clauses, make sure the variable is wrapped in %.
$em->createQuery("
SELECT r
FROM WeAdminBundle:FamilyRelation r
WHERE r.col like :query
")->setParameters(array(
'query' => '%'.$foo.'%'
))
In short: you can't the way you want it.
To do it you'd need something like $dql->setColumn(array('variable_column' => 'some_column_name')) just as the bindColumn method from PDO, but there's no equivalent method (bindColum or setcolumn) in Doctrine.
For use of different parameter instead of col please see below example:
$var = "r.col";
Here you can change based on condition.
$em->createQuery("
SELECT r
FROM WeAdminBundle:FamilyRelation r
WHERE ".$var." like :query
")
please have a look it.
I have a method in my User model:
def self.search(search)
where('last_name LIKE ?', "%#{search}%")
end
However, it would be nice for my users to be able to search for both first_name and last_name within the same query.
I was thinking to create a virtual attribute like this:
def full_name
[first_name, last_name].join(' ')
end
But is this efficient on a database level. Or is there a faster way to retrieve search results?
Thanks for any help.
Virtual attribute from your example is just class method and cannot be used by find-like ActiveRecord methods to query database.
Easiest way to retrive search result is modifying Search method:
def self.search(search)
q = "%#{query}%"
where("first_name + ' ' + last_name LIKE ? OR last_name + ' ' + first_name LIKE ?", [q, q])
end
where varchar concatenation syntax is compatible with your database of choice (MS SQL in my example).
The search functionality, in your example, is still going to run at the SQL level.
So, to follow your example, your search code might be:
def self.search_full_name(query)
q = "%#{query}%"
where('last_name LIKE ? OR first_name LIKE ?', [q, q])
end
NOTE -- these sorts of LIKE queries, because they have a wildcard at the prefix, will be slow on large sets of data, even if they are indexed.
One way this can be implemented is by tokenizing (splitting) the search query and creating one where condition per each token:
def self.search(query)
conds = []
params = {}
query.split.each_with_index do |token, index|
conds.push "first_name LIKE :t#{index} OR last_name LIKE :t#{index}"
params[:"t#{index}"] = "%#{token}%"
end
where(conds.join(" OR "), params)
end
Also make sure you prevent SQL injection attacks.
However, it's better to use full-text searching tools, such as ElasticSearch and its Ruby gem named Tire to handle searches.
EDIT: Fixed the code.
A scope can be made to handle complex modes, here's an example from one project I'm working on:
scope :search_by_name, lambda { |q|
if q
case q
when /^(.+),\s*(.*?)$/
where(["(last_name LIKE ? or maiden_name LIKE ?) AND (first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{$1}%","%#{$1}%","%#{$2}%","%#{$2}%","%#{$2}%"
])
when /^(.+)\s+(.*?)$/
where(["(last_name LIKE ? or maiden_name LIKE ?) AND (first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{$2}%","%#{$2}%","%#{$1}%","%#{$1}%","%#{$1}%"
])
else
where(["(last_name LIKE ? or maiden_name LIKE ? OR first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{q}%","%#{q}%","%#{q}%","%#{q}%","%#{q}%"
])
end
else
{}
end
}
As you can see, I do a regex match to detect different patterns an build different searches depending on what is provided. As an added bonus, if nothing is provided, it returns an empty hash which effectively is where(true) and returns all results.
As mentioned elsewhere, the db cannot index the columns when a wildcard is used on both sides like %foo%, so this could potentially get slow on very large datasets.
Per section 2.2 of rails guide on Active Record query interface here:
which seems to indicate that I can pass a string specifying the condition(s), then an array of values that should be substituted at some point while the arel is being built. So I've got a statement that generates my conditions string, which can be a varying number of attributes chained together with either AND or OR between them, and I pass in an array as the second arg to the where method, and I get:
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 5)
which leads me to believe I'm doing this incorrectly. However, I'm not finding anything on how to do it correctly. To restate the problem another way, I need to pass in a string to the where method such as "table.attribute = ? AND table.attribute1 = ? OR table.attribute1 = ?" with an unknown number of these conditions anded or ored together, and then pass something, what I thought would be an array as the second argument that would be used to substitute the values in the first argument conditions string. Is this the correct approach, or, I'm just missing some other huge concept somewhere and I'm coming at this all wrong? I'd think that somehow, this has to be possible, short of just generating a raw sql string.
This is actually pretty simple:
Model.where(attribute: [value1,value2])
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
# notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value)
Have a look at http://guides.rubyonrails.org/active_record_querying.html#array-conditions for more details on how this works.
Instead of passing the same parameter multiple times to where() like this
User.where(
"first_name like ? or last_name like ? or city like ?",
"%#{search}%", "%#{search}%", "%#{search}%"
)
you can easily provide a hash
User.where(
"first_name like :search or last_name like :search or city like :search",
{search: "%#{search}%"}
)
that makes your query much more readable for long argument lists.
Sounds like you're doing something like this:
Model.where("attribute = ? OR attribute2 = ?", [value, value])
Whereas you need to do this:
#notice the lack of an array as the last argument
Model.where("attribute = ? OR attribute2 = ?", value, value) Have a
look at
http://guides.rubyonrails.org/active_record_querying.html#array-conditions
for more details on how this works.
Was really close. You can turn an array into a list of arguments with *my_list.
Model.where("id = ? OR id = ?", *["1", "2"])
OR
params = ["1", "2"]
Model.where("id = ? OR id = ?", *params)
Should work
If you want to chain together an open-ended list of conditions (attribute names and values), I would suggest using an arel table.
It's a bit hard to give specifics since your question is so vague, so I'll just explain how to do this for a simple case of a Post model and a few attributes, say title, summary, and user_id (i.e. a user has_many posts).
First, get the arel table for the model:
table = Post.arel_table
Then, start building your predicate (which you will eventually use to create an SQL query):
relation = table[:title].eq("Foo")
relation = relation.or(table[:summary].eq("A post about foo"))
relation = relation.and(table[:user_id].eq(5))
Here, table[:title], table[:summary] and table[:user_id] are representations of columns in the posts table. When you call table[:title].eq("Foo"), you are creating a predicate, roughly equivalent to a find condition (get all rows whose title column equals "Foo"). These predicates can be chained together with and and or.
When your aggregate predicate is ready, you can get the result with:
Post.where(relation)
which will generate the SQL:
SELECT "posts".* FROM "posts"
WHERE (("posts"."title" = "Foo" OR "posts"."summary" = "A post about foo")
AND "posts"."user_id" = 5)
This will get you all posts that have either the title "Foo" or the summary "A post about foo", and which belong to a user with id 5.
Notice the way arel predicates can be endlessly chained together to create more and more complex queries. This means that if you have (say) a hash of attribute/value pairs, and some way of knowing whether to use AND or OR on each of them, you can loop through them one by one and build up your condition:
relation = table[:title].eq("Foo")
hash.each do |attr, value|
relation = relation.and(table[attr].eq(value))
# or relation = relation.or(table[attr].eq(value)) for an OR predicate
end
Post.where(relation)
Aside from the ease of chaining conditions, another advantage of arel tables is that they are independent of database, so you don't have to worry whether your MySQL query will work in PostgreSQL, etc.
Here's a Railscast with more on arel: http://railscasts.com/episodes/215-advanced-queries-in-rails-3?view=asciicast
Hope that helps.
You can use a hash rather than a string. Build up a hash with however many conditions and corresponding values you are going to have and put it into the first argument of the where method.
WRONG
This is what I used to do for some reason.
keys = params[:search].split(',').map!(&:downcase)
# keys are now ['brooklyn', 'queens']
query = 'lower(city) LIKE ?'
if keys.size > 1
# I need something like this depending on number of keys
# 'lower(city) LIKE ? OR lower(city) LIKE ? OR lower(city) LIKE ?'
query_array = []
keys.size.times { query_array << query }
#['lower(city) LIKE ?','lower(city) LIKE ?']
query = query_array.join(' OR ')
# which gives me 'lower(city) LIKE ? OR lower(city) LIKE ?'
end
# now I can query my model
# if keys size is one then keys are just 'brooklyn',
# in this case it is 'brooklyn', 'queens'
# #posts = Post.where('lower(city) LIKE ? OR lower(city) LIKE ?','brooklyn', 'queens' )
#posts = Post.where(query, *keys )
now however - yes - it's very simple. as nfriend21 mentioned
Model.where(attribute: [value1,value2])
does the same thing