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
Related
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!
I do a lot of time without date querying in my app, and I would like to abstract some of the queries away.
So say I have a model with a DateTime starts_at field:
Shift.where('starts_at::time > ?', '20:31:00.00')
-> SELECT "shifts".* FROM "shifts" WHERE (starts_at::time > '20:31:00.00')
This correctly returns all of the 'starts_at' values greater than the time 20:31.
I want to dynamically pass in the column name into the query, so I can do something like:
Shift.where('? > ?', "#{column_name}::time", '20:31:00.00').
-> SELECT "shifts".* FROM "shifts" WHERE ('starts_at::time' > '20:31:00.00')
In this example, this does not work as the search executes starts_at::time as a string, not as a column with the time cast.
How can I safely pass in column_name into a query with the ::time cast? While this will not accept user input, I would still like to ensure SQL injection is accounted for.
This is more complicated than you might think at first because identifiers (column names, table names, ...) and values ('pancakes', 6, ...) are very different things in SQL that have different quoting rules and even quote characters (single quotes for strings, double quotes for identifiers in standard SQL, backticks for identifiers in MySQL, brackets for identifiers in SQL-Server, ...). If you think of identifiers like Ruby variable names and values like, well, literal Ruby values then you can start to see the difference.
When you say this:
where('? > ?', ...)
both placeholders will be treated as values (not identifiers) and quoted as such. Why is this? ActiveRecord has no way of knowing which ? should be an identifier (such as the created_at column name) and which should be a value (such as 20:31:00.00).
The database connection does have a method specifically for quoting column names though:
> puts ActiveRecord::Base.connection.quote_column_name('pancakes')
"pancakes"
=> nil
so you can say things like:
quoted_column = Shift.connection.quote_column_name(column_name)
Shift.where("#{quoted_name}::time > ?", '20:31:00.00')
This is a little unpleasant because we recoil (or at least we should) at using string interpolation to build SQL. However, quote_column_name will take care of anything dodgy or unsafe in column_name so this isn't actually dangerous.
You could also say:
quoted_column = "#{Shift.connection.quote_column_name(column_name)}::time"
Shift.where("#{quoted_name} > ?", '20:31:00.00')
if you didn't always need to cast the column name to a time. Or even:
clause = "#{Shift.connection.quote_column_name(column_name)}::time > ?"
Shift.where(clause, '20:31:00.00')
You could also use extract or one of the other date/time functions instead of a typecast but you'd still be left with the quoting problem and the somewhat cringeworthy quote_column_name call.
Another option would be to whitelist column_name so that only specific valid values would be allowed. Then you could throw the safe column_name right into the query:
if(!in_the_whitelist(column_name))
# Throw a tantrum, hissy fit, or complain in your preferred fashion
end
Shift.where("#{column_name} > ?", '20:31:00.00')
This should be fine as long as you don't have any funky column names like "gotta have some breakfast" or similar things that always need to be properly quoted. You could even use Shift.column_names or Shift.columns to build your whitelist.
Using both a whitelist and then quote_column_name would probably be the safest but the quote_column_name method should be sufficient.
I decided to use this small solution, taking advantage of Rails column naming conventions:
scope :field_before_and_on_date, -> (field, time) do
column_name = field.to_s.parameterize.underscore
where("#{column_name} <= ?", time.end_of_day)
end
# Takes advantage of:
> "); and delete everything(); stuff(".parameterize.underscore
=> "and_delete_everything_stuff"
It's limited but the concept would work for a type cast too.
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")
I am storing product information, including the release year of the product, using hstore and postgresql in rails. Now I would like to be able to query for all products that was released before or after a specific year. I am able to query for all records containing the year field in the 'data' hstore column using:
Product.where("data ? 'year'")
Due to hstore the year value is stored as a string. Therefore, I have tried to type cast the year to an integer in order to find records with years greater than/less than X:
Product.where("(data ? 'year')::int > 2011")
However, this does not seem to work, I always get an empty array of results in return. What am I doing wrong? Is there another way to do this?
I think the operator your are looking for is ->.
So, try this : where("(data -> 'year')::int > 2011")
(from jO3w's comment)
My database has "spine numbers" and I want to sort by them.
#films = Film.all.sort{|a,b| a.id <=> b.id }
That is my one controller, but the spines go 1, 2, 3 ... 100, 101 etc. instead of 001,002,003... so the sorting is out of whack. There's probably an easy class for this something like:
#films = Film.all.sort{|a,b| a.id.abs <=> b.id.abs }
But I don't know it. Thanks for the help.
PS also, why has the rails wiki been down so often recently?
You should use Film.order("id DESC") (or "ASC") method which aplies SQL ORDER BY clause to the query.
By default, records are sorted by the primary key column, at least in MySQL.
If this hasn't answered your question, please provide some more information on your database.
Edited
Yes, I do see. The only thing that comes to mind is that you're using some kind of string datatype for the spine numbers column. In this case, this kind of sorting makes sense, because values are compared alpabetically char to char like this
1| |
0|5|4
2|5|
1|4|3
which'll return
054
1
143
25
while numeric values such as integer, or float, are compared by their actual value, and not by separate bytes.
So you should create a migration to change the datatype of your spine number to integer.