search by spliting the content of attributes in model -rails - ruby-on-rails

I have a model Lodging which has an attribute price_range this attribute contains data in this format 89;149 (in string format) say this is price range between 89 to 149 now I want to search those lodgings which have price range between 100-200 I want to do this by a single line query Like as Lodging.where(... is there any way which can solve my query?
Thanks!

Sorry but that's a bad database design. I suggest to migrate your implementation to use two db fields (may be: low_price, high_price). Then you could do a search with a single query.
Lodging.where(["low_pric >= ?" AND high_price <= ?], 100, 200)
May be for you the following could work with MySQL, but I'm not shure. I can't test it at the moment:
Lodging.
where(["(CONVERT(SUBSTRING(price_range, 1, LOCATE(';',price_range) -1) USING INTEGER) >= :low_price AND CONVERT(SUBSTRING(price_range, 1, LOCATE(';',price_range) -1) USING INTEGER) <= :low_price) OR (CONVERT(SUBSTRING(price_range, LOCATE(';',price_range) + 1) USING INTEGER) >= :high_price AND CONVERT(SUBSTRING(price_range, LOCATE(';',price_range) + 1) USING INTEGER) <= :high_price)", {:low_price => 100, :high_price => 200}])

First of all, I recommend to split db column or to change the column type(range! if you use postgresql)
There is no rails style query. you must use DB QUERY IN where clause.
like(postgresql),
Lodging.where("cast(regexp_split_to_array(price_range, ';')[0] as int) > 100").where("cast(regexp_split_to_array(price_range, ';')[1] as int) < 200")

Related

Rails + PostgreSQL: if value in one column is NULL, search by a value in a different column

In my model, I have these columns:
customer_invoiced_at: datetime
customer_invoice_at_custom: datetime
I am trying to search all records where the given date matches customer_invoiced_at:
scope :by_customer_invoiced_at_from, (lambda do |date_from|
self.where("customer_invoiced_at >= ?", date_from.to_datetime.beginning_of_day) if date_from.present?
end)
I'd need to tweak it a bit - if customer_invoice_at_custom exists (is not null or empty), I would need to use this field instead of customer_invoiced_at. However, if customer_invoice_at_custom is NULL or empty, I'd want to use customer_invoiced_at (as it is now in the shown scope).
How do I achieve that?
Thank you in advance
Can you use PostgreSQL's native COALESCE() function? This does exactly what you want:
.where("COALESCE(customer_invoiced_at, my_other_column) >= ?", date_from.to_datetime.beginning_of_day)

Is this the right Rails query when searching for a score?

I have the following query...
CourseRegistration.where(status: "Completed").where("score >= ?", "80")
First, yes, the score field is a string in the DB. This [mostly] works, however, scores of 100 are not being returned. I can query .where("score >= ?", "080") and it does return all scores from 80-100 as I want, but it does quite feel right. Is there another way I should be doing this? Or, maybe someone could take a stab at explaining exactly how this query is working so I feel better about it.
The way you should be doing this is to change the score column to an integer so that things work as expected. If you can't do that then you could cast the score in the query:
where("score::int >= ?", 80) # PostgreSQL-specific casting syntax
where("cast(score as int) >= ?", 80) # Standard SQL type cast
Of course, if the score column can contain non-numeric strings then you're going to get exceptions from this sort of query so you may need to account for that. How you account for such data depends on what strange data you have to deal with.
I would suggest you to change the score column type to integer. You can do that generating a migration like:
rails g migration ChangeScoreType
Then you edit your migration file like this:
class ChangeScoreType < ActiveRecord::Migration
change_column :course_registrations, :score, :integer, using: 'company_id::integer'
end
also, you can cast score as an INT on your query (should test). Something like this:
CourseRegistration.where(status: "Completed").where("CAST(Score AS INT) >= ?", "80")
Hope this helps, good luck!

Computing ActiveRecord nil attributes

In my Rails app I have something like this in one of the models
def self.calc
columns_to_sum = "sum(price_before + price_after) as price"
where('product.created_at >= ?', 1.month.ago.beginning_of_day).select(columns_to_sum)
end
For some of the rows we have price_before and or price_after as nil. This is not ideal as I want to add both columns and call it price. How do I achieve this without hitting the database too many times?
You can ensure the NULL values to be calculated as 0 by using COALESCE which will return the first non NULL value:
columns_to_sum = "sum(COALESCE(price_before, 0) + COALESCE(price_after, 0)) as price"
This would however calculate the sum prices of all products.
On the other hand, you might not have to do this if all you want to do is have an easy way to calculate the price of one product. Then you could add a method to the Product model
def.price
price_before.to_i + price_after.to_i
end
This has the advantage of being able to reflect changes to the price (via price_before or price_after) without having to go through the db again as price_before and price_after will be fetched by default.
But if you want to e.g. select records from the db based on the price you need to place that functionality in the DB.
For that I'd modulize your scopes and join them again later:
def self.with_price
columns_to_sum = "(COALESCE(price_before, 0) + COALESCE(price_after, 0)) as price"
select(column_names, columns_to_sum)
end
This will return all records with an additional price reader method.
And a scope independent from the one before:
def self.one_month_ago
where('product.created_at >= ?', 1.month.ago.beginning_of_day)
end
Which could then be used like this:
Product.with_price.one_month_ago
This allows you to continue modifying the scope before hitting the DB, e.g. to get all Products where the price is higher than x
Product.with_price.one_month_ago.where('price > 5')
If you are trying to get the sum of price_before and price_after for each individual record (as opposed to a single sum for the entire query result), you want to do it like this:
columns_to_sum = "(coalesce(price_before, 0) + coalesce(price_after, 0)) as price"
I suspect that's what you're after, since you have no group in your query. If you are after a single sum, then the answer by #ulferts is correct.

Sum active record with expression rails

I have this code in my sql view:
SELECT
reports.`date`
reports.book_title
SUM( reports.net_sold_count * reports.avg_list_price / 10 ) AS revenue
FROM
reports
GROUP BY
reports.date,
reports.book_title
I need to translate it to ruby code
I have active record model
class Report < ActiveRecord::Base
Right now I have this code:
Report.group(:date, :book_title)
But I don't know where to go next because .sum in all examples accepts only one argument.(http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-sum)
UPD:
Right now, I am thinking of trying something like
Report.select('date, book_title, SUM( reports.net_sold_count * reports.avg_list_price / 10 ) AS revenue').group(:date, :book_title)
The #sum documentation is misleading. Yes, #sum only accepts one argument but that argument doesn't have to be a symbol corresponding to a column name, that argument can also be an SQL expression that uses the available columns. In particular, the argument to #sum could be the Ruby string:
'reports.net_sold_count * reports.avg_list_price / 10'
and so you could say:
Report.group(:date, :book_title).sum('reports.net_sold_count * reports.avg_list_price / 10')
That will give you a Hash that looks like:
{
[some_date, some_title] => some_sum,
...
}
that you can pull apart as needed back in Ruby land.

Rails/Postgres treat string column as integer

I got a table named companies and a column named employees, which is a string column.
My where condition to find companies which have between 10 and 100 employees:
where("companies.employees >= ? AND companies.employees <= ?", 10, 100)
The problem is: The column needs to remain a string column so I can't just convert it to integer but I also want to compare the employee numbers. Is there any way to do this?
This may work, it is a ruby question, I don't know ruby :-) In postgres I would write the query as Craig says, like this:
select * from companies where employees::integer >= 10 and employees::integer <= 100;
(Of course there is substitution, etc, but this gets the concept across. One of the problems you run in to when you don't use the correct type in postgres is that indices don't work right. Since you are casting the employees to an integer type, you have to fetch every record, convert it to an integer, then filter using the greater/less than stuff. Every record in the table will be fetched, casted, then compared. If this was an integer type to start with, and there was an index on the table, then the postgres engine can do a lot better performance wise by selecting only the relevant records. Anyway...
Your ruby may work modified like this:
where("companies.employees::integer >= ? AND companies.employees::integer <= ?", 10, 100)
But, that makes me curious about the substitution. If the type is gleaned from the type of the argument, then it might work because the 10 and 100 are clearly integers. If the substitution gets weird, you might be able to do this:
where("companies.employees::integer >= cast(? as integer) AND companies.employees::integer <= cast(? as integer)", 10, 100)
You can use that syntax for the entire query as well:
where("cast(companies.employees as integer) >= cast(? as integer) AND cast(companies.employees as integer) <= cast(? as integer)", 10, 100)
One of these variants might work. Good Luck.
-g

Resources