Ecto where like query acts like where == - phoenix-framework

I'm trying to get an ecto like query working like this:
def find(searchterm) do
query = from c in Contact,
#where: fragment("? % ?", c.company_name, ^searchterm),
where: like(c.company_name, ^searchterm),
contacts = Repo.all(query)
{:ok, contacts}
end
In my table, I have a company_name "Asymptote". Using where: like/2 my query looks like this:
SELECT c0."id", c0."company_id", c0."company_name" FROM "contacts" AS c0 WHERE (c0."company_name" LIKE $1) ["Asym"] (1.0ms)
when the pg_trm search uncommented, it looks like this:
SELECT c0."id", c0."company_id", c0."company_name" FROM "contacts" AS c0 WHERE (c0."company_name" % $1) ["Asym"] (1.0ms)
As far as I can see, the queries look good, but there are no results. Since I added the index after adding "Asymptote" to the database, I expect that is why it isn't found in the pg_trm index, but why won't like/2 or ilike/2 work? When entering in the full name "Asymptote", I am able to find the record.

I faced some similar problem. Unfortunately I had no pg_trgm available. I used the LIKE as in:
from candidate in query,
where: like(candidate.first_name, ^("%#{text}%"))
This matched the text in any place of the candidate.first_name.

With the help of Mitchell Henke of Rokkincat, here's a way to get pg_trgm working with custom match percentages:
def find(searchterm) do
limit = 0.1
query = from c in Contact,
where: fragment("similarity(?, ?) > ?", c.company_name, ^searchterm, ^limit),
#where: like(c.company_name, ^searchterm),
contacts = Repo.all(query)
{:ok, contacts}
end
But I still can't get like/2 working. Also, I don't see where in Ecto source this function exists.

Related

multiple where statements combined with OR in Activerecord and Ruby

I would like to do a query with activerecord (not rails) with multiple keywords that are contained in a field (so I have to use LIKE) but I don't know in advance how many keywords there will be.
My query looks like this, Word is my model.
query = ['word1','word2'] #could be more
puts "searching for #{query}"
qwords = Word.none
query.each do |qword|
puts qwords.where("word like ?", "%#{qword}%").to_sql
qwords = qwords.where("word like ?", "%#{qword}%")
end
Which gives nothing because the queries are added as AND but I need OR.
searching for ["word1", "word2"]
SELECT "words".* FROM "words" WHERE (word like '%word1%')
SELECT "words".* FROM "words" WHERE (word like '%word1%') AND (word like '%word2%')
#<ActiveRecord::Relation []>
I can't use Word.where(word: query) which uses the sql IN keyword because that only works for exact matches.
Is there a solution that doesn't involves concatenating the whole SQL that is needed ?
query = "word1 word2" #could be more
puts "searching for #{query}"
query_length = query.split.length #calculates number of words in query
Now you can put together the number of SQL queries you need regardless of the number of keywords in your query
Word.where([(['word LIKE ?'] * query_length).join(' OR ')] + (query.split.map {|query| "%#{query}%"}))
This should return
["word LIKE ? OR word LIKE ?", "%word1%", "%word2%"]
for your SQL search
Had forgotten about this question and found a solution myself afterward.
I now do the following. The problem was caused by using the resultset to do my next query on while like this it is on the whole recordset and the results are added.
#qwords = Word.none
$query.each do |qword|
#qwords += Word.where(word: qword)
end

how can I use like query in ruby with sinatra?

Here's the query I tried:
#blogs = DB[:blogs].where(:title => params[:s_txt]).reverse_order(:id)
In this query, I'd like to find blogs in my database. I also need to create a query that gives users more results. How can I do this?
It is better to use dynamic parameters to avoid SQL injection:
#blogs = DB[:blogs].where("title LIKE ?", "%#{params[:s_txt]}%").reverse_order(:id)
or
#blogs = DB[:blogs].where("title LIKE :text", text: "%#{params[:s_txt]}%").reverse_order(:id)
You can easily add more parameters to this:
#blogs = DB[:blogs].where("title LIKE ? OR content LIKE ?", "%#{params[:s_txt]}%", "%#{params[:s_txt]}%").reverse_order(:id)
or
#blogs = DB[:blogs].where("title LIKE :text OR content LIKE :text", text: "%#{params[:s_txt]}%").reverse_order(:id)
I also had this problem and only found this solution
#blogs = DB[:blogs].where("title LIKE '%#{params[:s_txt]}%'").reverse_order(:id)
Curious if there are better ways..
Searching in two fields is repeating the LIKE and seperating by AND or OR
#blogs = DB[:blogs].where("title LIKE '%#{params[:s_txt]}%'
or comment LIKE '%#{params[:c_txt]}%'").reverse_order(:id)
or do it like this
#blogs = DB[:blogs].where("title||comment LIKE '%#{params[:s_txt]}%'").reverse_order(:id)
Tested in Sqlite and it works. One advise: in lage tables you 'd better drop the first % if possible because the database will do a full table scan otherwise without using indexes but you can find all that stuff in the sql questions.

select two calculations with one query in active record

In SQL I would do a "select count(*), min(price) from products". What is the best way to do this with one query in active records?
Right now I have to do two queries, which doesn't feel right.
result_count = Product.where(filter_string).count
result_min = Product.where(filter_string).minimum(:price)
You can add the string in a select method like this:
result_stats = Product.select("count(*) as product_count, min(price) as price_min").where(filter_string)[0]
result_stats["product_count"] # => 123
result_stats["price_min"] # => 12.35
The limitation here is that this will initialize Product objects with only those fields accessible (in this case only 1 object since there's no group by clause). In this case, it's not really an issue, but something worth knowing if you get an error when you try to access relations.
http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields

ActiveRecord 4 cannot retrieve "select AS" field

Ok, I feel really stupid for asking this, but it's driving me nuts and I can't figure it out. The docs say I should be able to use select AS in a Rails/ActiveRecord query. So:
d = Dvd.where(id: 1).select("title AS my_title")
Is a valid query and if I do a to_sql on it, it produces the expected SQL:
SELECT title AS my_title FROM `dvd` WHERE `dvd`.`id` = 1
However, d.my_title will give an error:
NoMethodError: undefined method `my_title' for #<ActiveRecord::Relation
I need to be able to use AS since the columns I want to retrieve from different joins have the same name so I can't access them the "regular" way and have to resort to using AS.
I also don't want to resort to using find_by_sql for future compatibility and a possible switch form Mysql to PostGresql.
Just to clarify, what I'm really trying to do is write this SQL in a Railsy way:
SELECT tracks.name AS track_name, artists.name AS artist_name, composers.name AS composer_name, duration
FROM `tracks_cds`
INNER JOIN `tracks` ON `tracks`.`id` = `tracks_cds`.`track_id`
INNER JOIN `artists` ON `artists`.`id` = `tracks_cds`.`artist_id`
INNER JOIN `composers` ON `composers`.`id` = `tracks_cds`.`composer_id`
WHERE cd_id = cd.id
The top example was just a simplification of the fact that SELECT AS will not give you an easy way to refer to custom fields which I find hard to believe.
ActiveRecord automatically creates getter and setter methods for attributes based on the column names in the database, so there will be none defined for my_title.
Regarding the same common names, why not just do this:
d = Dvd.where(id: 1).select("dvds.title")
You can write your sql query and then just pass into ActiveRecord's execute method
query = "SELECT title AS my_title FROM `dvd` WHERE `dvd`.`id` = 1"
result = ActiveRecord::Base.connection.execute(query)

Rails selecting aliases in query

So I have 2 tables actor and actor2role. The latter is a lookup (junction) table to relate actor, role and dvd. I need to create a query with aliases, so I have this method:
def self.remove_duplicate_by_id(id)
offendingActor = self.find(id).actor # get the actor's name
ids = self.find_by_sql("SELECT MIN(id) AS minId, MAX(id) AS maxId, actor FROM `dvd_actor` WHERE actor = '#{offendingActor}'")
rolesForOffender = ids.actor2role # throws error
end
The problem is that ids is not an ActiveRecord object so I can't use the actor2role method (which is a relationship I've established between the 2 tables in Rails and works when you do something like Actor.first.actor2role.
so the questions is: Am I doomed to do this manually and then issue another sql query to recreate what the actor2role method would accomplish or is there some way to do this with Rails objects?
I'd really like to do it all natively if possible because I also have to issue these queries:
UPDATE dvd_actor2role SET actorid = $d->minId WHERE actorId = $d->maxId");
DELETE FROM dvd_actor2role WHERE actorId = $d->maxId LIMIT 1");
Is this even possible?
In the end I went with this which seems to do the trick. If anyone can spot any code that could be optimized, or something inherently wrong (and feels like chiming in) please feel free to comment.
actorObject = self.find_by_id(id) # get the object because we need it below for other queries
offendingActor = actorObject.actor
ids = self.select("MIN(id) AS minId, MAX(id) AS maxId, id, actor").find_by_actor(offendingActor)
rolesForOffender = actorObject.actor2role
rolesForOffender.each do |r|
# first find out if the relationship already exists or we get a SQL error for the foreign key relationship.
exists = Actor2role.where("actorId = ? AND roleId = ?", ids.minId, r.roleId)
if exists.nil?
Actor2role.update_all("actorId = #{ids.minId}, actorId = #{ids.maxId}")
end
end
self.destroy(ids.maxId) # delete this guy in actor table
end

Resources