I am having a problem performing a where query against an array field in my Postgres database.
In my rails app i have a table called People. One column in this is called pets. Now this column contains array values, ie:
["dog", "cat", "fish"]
I would like to perform a query that returns all the people that have a pet dog for example.
The solution ive been using so far looks as such
People.where("\"pets\" #> '{\"" + checkedPet + "\"}'")
where checkedPet is a variable and could be "dog" or any other animal.
This works but i feel is vulnerable to a SQL injection problem?
Is this the case? If so what is a better and safer solution to avoid it?
According to ActiveRecord and PostgreSQL guide you can do the following:
People.where('? = any("pets")', checkedPet)
Or
People.where('"pets" #> ?', "{#{checkedPet}}")
Related
I have a postgres table with jsonb array elements and I'm trying to do sql queries to extract the matching elements. I have the raw SQL query running from the postgres command line interface:
select * from movies where director #> any (array ['70', '45']::jsonb[])
This returns the results I'm looking for (all records from the movies table where the director jsonb elements contain any of the elements in the input element).
In the code, the value for ['70, '45'] would be a dynamic variable ie. fixArr and the length of the array is unknown.
I'm trying to build this into my Bookshelf code but haven't been able to find any examples that address the complexity of the use case. I've tried the following approaches but none of them work:
models.Movies.where('director', '#> any', '(array' + JSON.stringify(fixArr) + '::jsonb[])').fetchAll()
ERROR: The operator "#> any" is not permitted
db.knex.raw('select * from movies where director #> any(array'+[JSON.stringify(fixArr)]+'::jsonb[])')
ERROR: column "45" does not exist
models.Movies.query('where', 'director', '#>', 'any (array', JSON.stringify(fixArr) + '::jsonb[])').fetchAll()
ERROR: invalid input syntax for type json
Can anyone help with this?
As you have noticed, knex nor bookshelf doesn't bring any support for making jsonb queries easier. As far as I know the only knex based ORM that supports jsonb queries etc. nicely is Objection.js
In your case I suppose better operator to find if jsonb column contains any of the given values would be ?|, so query would be like:
const idsAsString = ids.map(val => `'${val}'`).join(',');
db.knex.raw(`select * from movies where director \\?| array[${idsAsString}]`);
More info how to deal with jsonb queries and indexing with knex can be found here https://www.vincit.fi/en/blog/objection-js-postgresql-power-json-queries/
No, you're just running into the limitations of that particular query builder and ORM.
The easiest way is using bookshelf.Model.query and knex.raw (whereRaw, etc.). Alias with AS and subclass your Bookshelf model to add these aliased attributes if you care about such things.
If you want things to look clean and abstracted through Bookshelf, you'll just need to denormalize the JSONB into flat tables. This might be the best approach if your JSONB is mostly flat and simple.
If you end up using lots of JSONB (it can be quite performant with appropriate indexes) then Bookshelf ORM is wasted effort. The knex query builder is only not a waste of time insofar as it handles escaping, quoting, etc.
I have a entity in my database that have multiple fields.
I need search in this entity based if a certain input it's contained in certain fields, by example
Entity Car
model_name
owner
I want search like this (a pseudo sql idea)
Select * FROM Car WHERE (LIKE model_name = "%maz%" OR LIKE owner = "%maz%")
I've tried with the find method but it's not work
searched_string = "%maz%"
Car.find(:all, :conditions => {model_name: searched_string, owner: searched_string})
How to achieve this?
When using the conditions option in an ActiveRecord query, the resulting SQL will be generated for an exact match. Additionally, each condition you add will be joined using an AND, not an OR.
That being said, the SQL generated by your query above likely looks like this:
SELECT * FROM Car WHERE (model_name = "%maz%" AND owner = "%maz%")
To get the query you are looking for, you'll have to hand-write the SQL for the WHERE clause.
Car.where("model_name ILIKE :q OR owner ILIKE :q", q: "%maz%")
NOTE: I'm using the more modern ActiveRecord syntax above.
Some good news is that Rails 5 will include support for "or" queries.
I am trying to learn rails [by following the SAAS course in coursera] and working with simple Movie table using ActiveRecord.
I want to display all movies with title sorted. I would like it to be sorted case insensitively.
I tried doing it this way:
Movie.all(:conditions => ["lower(title) = ?", title.downcase],:order => "title DESC")
=>undefined local variable or method `title' for #<MoviesController:0xb4da9a8>
I think it doesnt recognise lower(title) .
Is this the best way to achieve case insesisitve sort ?
Thanks!
Use where and not all
Movie.where("lower(title) = ?", title.downcase).order("title DESC")
Don't really understand the sort though. Here you'll get all movies with lower title equalling to title.downcase. Everything is equal, how could you sort it by title desc ?
To sort reverse-alphabetically all movies by lowercase title :
Movie.order("lower(title) DESC").all
You have to do this:
Movie.order("lower(title) DESC").all
A more robust solution is to use arel nodes. I'd recommend defining a couple scopes on the Movie model:
scope :order_by_title, -> {
order(arel_table['title'].lower.desc)
}
scope :for_title, (title)-> {
where(arel_table['title'].lower.eq title.downcase)
}
and then call Movie.for_title(title).order_by_title
The advantage over other answers listed is that .for_title and .order_by_title won't break if you alias the title column or join to another table with a title column, and they are sql escaped.
Like rickypai mentioned, if you don't have an index on the column, the database will be slow. However, it's bad (normal) form to copy your data and apply a transform to another column, because then one column can become out of sync with the other. Unfortunately, earlier versions of mysql didn't allow for many alternatives other than triggers. After 5.7.5 you can use virtual generated columns to do this. Then in case insensitive cases you just use the generated column (which actually makes the ruby more straight forward).
Postgres has a bit more flexibility in this regard, and will let you make indexes on functions without having to reference a special column, or you can make the column a case insensitive column.
Having MySQL perform upper or lower case operation each time is quite expensive.
What I recommend is having a title column and a title_lower column. This way, you can easily display and sort with case insensitivity on the title_lower column without having MySQL perform upper or lower each time you sort.
Remember to index both or at least title_lower.
We have an SQL query in our Rails 3 app.
#followers returns an array of IDs of users following the current_user.
#followers = current_user.following
#feed_items = Micropost.where("belongs_to_id IN (?)", #followers)
Is there a more efficient way to do this query?
The query you have can't really be optimized anymore than it is. It could be made faster by adding an index to belongs_to_id (which you should almost always do for foreign keys anyway), but that doesn't change the actual query.
There is a cleaner way to write IN queries though:
Micropost.where(:belongs_to_id => #followers)
where #followers is an array of values for belongs_to_id.
It looks good to me.
However if you're looking for real minimum numer of characters in the code, you could change:
Micropost.where("belongs_to_id IN (?)", #followers)
to
Micropost.where("belongs_to_id = ?", #followers)
which reads a little easier.
Rails will see the array and do the IN.
As always the main goal of the ruby language is readability so little improvements help.
As for query being inefficent, you shuld check into indexs on that field.
They tend to be a little more specific for each db - you have only specified generic sql. in your question.
In Ruby on rails 3 I want to query on a has_many field of a model as follows:
#project.items.where(:status => 1)
The problem is I'm trying to get the exact opposite result than this. What i want is all items of #project where the status is not 1. Been looking for the answer to this for a while, anyone?
There are many ways to accomplish what you are trying to do, however, some are better than others. If you will always be searching for a hardcoded number (i.e. 1 in this case), then the following solution will work:
#project.items.where('status != 1')
However, if this value is not hard-coded, you are openly vulnerable to SQL injection as Rails will not (cannot) escape this kind of query. As a result, it is preferred among Rails developers to user the following syntax for most custom conditions (those that can't be constructed via Hash):
#project.items.where(['status != ?', 1])
This syntax is slightly confusing, so let me go over it. Basically you are providing the where clause an Array of values. The first value in the array is a String representing the query you want executed. Anywhere you want a value in that string, you place a ?. This serves as a placeholder. Next, you add an element for every question mark in you query. For example, if I had the following:
where(['first_name = ? AND last_name = ?', params[:first_name], params[:last_name]]
Rails will automatically match these up forming the query for you. In that process, it also escapes potentially unsafe characters, preventing injection.
In general, it is preferred to use the Array syntax, even for a hardcoded value. I've been told that pure string conditions in Rails 3.5 will raise a warning (unverified), so it doesn't hurt to get in the process of using the Array syntax now.