Rails active record query using MySQL UDF - mysql

I am using MySQL user-defined functions in my Rails app (levenshtein ratio related) for selecting similar strings. For example, i am selecting similar models ordering by "similarity" like that:
#something = Something.select("*", "levenshtein_ratio(name, '#{Mysql2::Client.escape(name)}') as l_ratio").having("l_ratio > ?", minratio).order("l_ratio DESC")
It works fine, there are two questions though:
Is there a better, more "native" way to do the same thing?
How can i get l_ratio value from the selected objects? ActiveRecord object won't contain it.

Related

Can you construct an ActiveRecord scope with a variable query string?

Setup:
I'm using Ruby on Rails with ActiveRecord and MySQL.
I have a Coupon model.
It has an attribute called query, it is a string which could be run with a where.
For example:
#coupon.query
=> "'http://localhost:3003/hats' = :url OR 'http://localhost:3003/shoes' = :url"`
If I were to run this query it would either pass or fail based on the :url value I pass in.
# passes
Coupon.where(#coupon.query, url: 'http://localhost:3003/hats')
Coupon.where(#coupon.query, url: 'http://localhost:3003/shoes')
# fails
Coupon.where(#coupon.query, url: 'http://localhost:3003/some_other_url')
This query varies between Coupon models, but it will always be compared to the current url.
I need a way to say: Given an ActiveRecord collection #coupons only keep coupons with queries that pass.
The structure of the where is always the same, but the query changes.
Is there any way to do this without a loop? I could potentially have a lot of coupons and I am hoping to do this an ActiveRecord scope. Something like this?
#coupons.where(self.query, url: #url)
Perhaps I need to write a user defined function in my database?
Using multiple variables in a query is easy, but where the thing you are comparing your variable to is also a variable - that has me stumped. Any suggestions very appreciated.
I would agree with Les Nightingill's comment that this looks like something that should probably be solved at a more architectural level. I'd imagine an easy refactoring to extract a new CouponQuery model that's a 1:n table containing multiple entries for a coupon_id for each query url that should pass. Then you could use a simple join like
Coupon.joins(:coupon_query).where(coupon_queries: { url: my_url })
If adding a new table is not an option, and if you're running on a newer MySQL version (>= 5.7), you could consider transforming the query column (or adding a new json_query column) into a MySQL JSON field and using the new JSON_CONTAINS query.
If from the user-side they should be able to manage the queries as a plain text field, you could use a before_save hook on your model to translate this into the separate table structure or JSON format respectively.
But if neither is an option for you and you really need to stick with the query column that stores a plain string, then you could use a LIKE query to match the sub-string 'your-url' = :url:
Coupon.where('url LIKE "%? = :url%"', my_url)
which, if you e.g. pass 'http://localhost:3003/hats' as my_url would return something like this SQL query:
SELECT `coupons`.* FROM `coupons`
WHERE (url LIKE "%'http://localhost:3003/hats' = :url%")

TypeORM: how to load relations with CreateQueryBuilder, without using JOINs?

I'm developing an API using NestJS & TypeORM to fetch data from a MySQL DB. Currently I'm trying to get all the instances of an entity (HearingTonalTestPage) and all the related entities (e.g. Frequency). I can get it using createQueryBuilder:
const queryBuilder = await this.hearingTonalTestPageRepo
.createQueryBuilder('hearing_tonal_test_page')
.innerJoinAndSelect('hearing_tonal_test_page.hearingTest', 'hearingTest')
.innerJoinAndSelect('hearingTest.page', 'page')
.innerJoinAndSelect('hearing_tonal_test_page.frequencies', 'frequencies')
.innerJoinAndSelect('frequencies.frequency', 'frequency')
.where(whereConditions)
.orderBy(`page.${orderBy}`, StringToSortType(pageFilterDto.ascending));
The problem here is that this will produce a SQL query (screenshot below) which will output a line per each related entity (Frequency), when I want to output a line per each HearingTonalTestPage (in the screenshot example, 3 rows instead of 12) without losing its relations data. Reading the docs, apparently this can be easily achieved using the relations option with .find(). With QueryBuilder I see some relation methods, but from I've read, under the hood it will produce JOINs, which of course I want to avoid.
So the 1 million $ question here is: is it possible with CreateQueryBuilder to load the relations after querying the main entities (something similar to .find({ relations: { } }) )? If yes, how can I achieve it?
I am not an expert, but I had a similar case and using:
const qb = this.createQueryBuilder("product");
// apply relations
FindOptionsUtils.applyRelationsRecursively(qb, ["createdBy", "updatedBy"], qb.alias, this.metadata, "");
return qb
.orderBy("product.id", "DESC")
.limit(1)
.getOne();
it worked for me, all relations are correctly loaded.
ref: https://github.com/typeorm/typeorm/blob/master/src/find-options/FindOptionsUtils.ts
You say that you want to avoid JOINs, and are seeking an analogue of find({relations: {}}), but, as the documentation says, find({relations: {}}) uses under the hood, expectedly, LEFT JOINs. So when we talk about query with relations, it can't be without JOIN's.
Now about the problem:
The problem here is that this will produce a SQL query (screenshot
below) which will output a line per each related entity (Frequency),
when I want to output a line per each HearingTonalTestPage
Your query looks fine. And the result of the query, also, is ok. I think that you expected to have as a result of the query something similar to json structure(when the relation field contains all the information inside itself instead of creating new rows and spread all its values on several rows). But that is how the SQL works. By the way, getMany() method should return 3 HearingTonalTestPage objects, not 12, so what the SQL query returns should not worry you.
The main question:
is it possible with CreateQueryBuilder to load the relations after
querying the main entities
I did't get what do you mean by saying "after querying the main entities". Can you provide more context?

Switched MySQL to Postgres: "column must appear in the GROUP BY clause or be used in an aggregate function"

Switching a rails app from MySQL to Postgres gives the following error:
ERROR: column "contacts.id" must appear in the GROUP BY clause or be used in an aggregate function
Here is the scope in question:
class User < MyModel
def self.top_contacts(timeframe = 1.week.ago, limit = 5)
Contact.unscoped
.where('created_at between ? and ?', timeframe, Time.now)
.group(:user_id)
.order('sum(score) DESC')
.limit(limit)
.includes(:user)
.collect{|x| x.user}
end
end
How to fix this?
Isn't using Rails as the database abstraction layer ensure switching the database should work seamlessly?
The problem is on the level of the SQL, which is invisible from your ORM layer. The problem is exactly with the RoR ORM, because it seems to generate a MySQL-friendly query which uses an extraordinary feature of the MySQL, which postgresql don't have.
The quick solution: give contacts.id to the columns by which you are GROUP-ing as well:
.group("user_id, contacts.id");

Rails SQLite vs MySQL Booleans

I'm working with Rails 3.2. My development box is using SQLite3 and my production host is using MySQL. Rails SQLite ActiveRecord connector will not save booleans as 1 or 0 and will only save it as 't' or 'f'. Of course I want DB neutral code but I cannot find any way around the following. I have a user model and a shift model. In this query I need all the shifts (work schedules) and I need to order the results by the related table as well as apply the boolean conditions.
#sh= Shift.find(:all, :include=>:user, :order=>'users.rating DESC', :conditions=>["a1=1 or a1='t'"])
I have also learned about ActiveRecord::Base.connection.quoted_true and quoted_false. I suspect I could change them but that also seems non portable and would probably be silently overridden if I upgrade.
I don't want to test for both 1 and 't' (or 0 and 'f'). Is there any way around doing this besides changing my dev environment to mysql?
You should be able to pass the following as a conditions:
:conditions => [["a1 = ?", true]]

Cannot get information in mysql result with rails

I'm using Rails with ActiveAdmin gem. And I want to select some information from mysql database.
sql = ActiveRecord::Base.connection();
s="SELECT word FROM dics WHERE word LIKE 'tung%'";
ten = sql.execute(s);
But when I printed out "ten" to screen, it showed that:
#<Mysql2::Result:0x4936260>
How can I get the information of records?
I suggest that you don't use ActiveRecord::Base.connection directly. Sticking with ARel syntax should work for most cases, and your example doesn't seem like an edge case.
As stated in the comments above, try the following:
dics = Dic.select(:word).where(["word LIKE ?", "tung%"]).all
In order to pluck some special field of object, not objects themselves, use pluck instead of all:
# instead of .pluck(:word) use real field identifier
dics = Dic.where(["word LIKE ?", "tung%"]).pluck(:word)