I've some problem with an SQL Query
I've users which have skills
def skills=(value)
super(value.reject(&:blank?).join("|")) if !value.nil?
end
def skills
super.present? ? super.split("|") : []
end
Here is my problem. When I query on this field with "like", word order matter. How can I avoid that ?
User.where("skills LIKE '%car%driving_license%'")
Each user with skills which at least contain car and driving_license
User.where("skills LIKE '%driving_license%car%'")
Each user with skills which at least contain car and driving_license but in a different order
I want to be able to avoid this order problem. Any help ?
If you sort the skills you can know the order and assemble a like query that will match both of them. You can create a migration to sort the existing skills.
def skills=(value)
super(value.reject(&:blank?).sort.join("|")) if value
end
rails g migration sortUserSkills
User.where.not(skills: nil).each do u
u.skills = u.skills
u.save
end
LIKE + OR queries are tough if my memory serves me. You can try ARel, but I think you lose LIKE.
2 things I can think of:
Do the queries separately and combine results.
Use something like this which may not work:
conditions = ['%driving_license%car%', '%car%driving_license%']
User.where("skills LIKE ?", conditions)
Related
I'm having trouble figuring out how to combine to queries into one with my Rails 5 app (mySQL). I want #activities to return the Activity records that meet the criteria for Query 1 or 2.
Can someone please help me? Thank you!
Query 1
#activities = Activity.where(owner_id: current_user.friend_ids, owner_type: "User")
Query 2
#activities = Activity.where(recipient_id: current_user.id)
Try this ............
#activities = Activity.where("(owner_id in = ? and owner_type = ?) or recipent_id = ?", current_user.friend_ids,"User",current_user.id )
For Rails 5 :
#activities = Activity.where(owner_id: current_user.friend_ids, owner_type: "User").or(Activity.where(recipient_id: current_user.id))
Hope this will work for you.
Try the below
#activities = Activity.where("(owner_id in = ? and owner_type = ?) or recipent_id = ?", current_user.friend_ids,"User",current_user.id )
or
#activities = Activity.where(owner_id: current_user.friend_ids, owner_type: "User").or(Activity.where(recipient_id: current_user.id))
using AREL is a great way to get OR or AND queries going, its a more programatic way of getting the SQL queries you want as you can build them up and meta program them in easily, and it avoids using direct SQL strings which can get too big and complex and unreadable, but here is a snippet for your code to get you going:
table = Activity.arel_table
query = arel_table['owner_id'].in(current_user.friend_ids).and(arel_table['owner_type'].eq("User"))
query = query.or(arel_table['recipient_id'].eq(current_user.id))
#activities = Activity.where(query)
I strongly recommend looking up more about using AREL, here are some pages to see more:
definitive guide to arel
using arel to compose sql queries
EDIT
For some reason I decided to completely ignore the fact that you are runnin rails 5, in which case Akshay's answer / Chakreshwar Sharma's second answer is definately the way to go.
However I still recommend learning about and getting to grips with AREL is it can really help out in a lot of other cases where you might have more complex queries to write!
you can also use this way:
# load all ids for owner
activity_ids_by_owner = Activity.where(owner_id: current_user.friend_ids, owner_type: "User").pluck(:id)
# load all ids for recipient
activity_ids_by_recipient = Activity.where(recipient_id: current_user.id).pluck(:id)
# load all activities for founded ids
Activity.where(id: activity_ids_by_owner | activity_ids_by_recipient)
I want to update all of a column in a table with over 2.2 million rows where the attribute is set to null. There is a Users table and a Posts table. Even though there is a column for num_posts in User, only about 70,000 users have that number populated; otherwise I have to query the db like so:
#num_posts = #user.posts.count
I want to use a migration to update the attributes and I'm not sure whether or not it's the best way to do it. Here is my migration file:
class UpdateNilPostCountInUsers < ActiveRecord::Migration
def up
nil_count = User.select(:id).where("num_posts IS NULL")
nil_count.each do |user|
user.update_attribute :num_posts, user.posts.count
end
end
def down
end
end
In my console, I ran a query on the first 10 rows where num_posts was null, and then used puts for each user.posts.count . The total time was 85.3ms for 10 rows, for an avg of 8.53ms. 8.53ms*2.2million rows is about 5.25 hours, and that's without updating any attributes. How do I know if my migration is running as expected? Is there a way to log to the console %complete? I really don't want to wait 5+ hours to find out it didn't do anything. Much appreciated.
EDIT:
Per Max's comment below, I abandoned the migration route and used find_each to solve the problem in batches. I solved the problem by writing the following code in the User model, which I successfully ran from the Rails console:
def self.update_post_count
nil_count = User.select(:id).where("num_posts IS NULL")
nil_count.find_each { |user|
user.update_column(:num_posts, user.posts.count) if user.posts
}
end
Thanks again for the help everyone!
desc 'Update User post cache counter'
task :update_cache_counter => :environment do
users = User.joins('LEFT OUTER JOIN "posts" ON "posts.user_id" = "users.id"')
.select('"users.id", "posts.id", COUNT("posts.id") AS "p_count"')
.where('"num_posts" IS NULL')
puts "Updating user post counts:"
users.find_each do |user|
print '.'
user.update_attribute(:num_posts, user.p_count)
end
end
First off don't use a migration for what is essentially a maintenance task. Migrations should mainly alter the schema of your database. Especially if it is long running like in this case and may fail midway resulting in a botched migration and problems with the database state.
Then you need to address the fact that calling user.posts is causing a N+1 query and you instead should join the posts table and select a count.
And without using batches you are likely to exhaust the servers memory quickly.
You can use update_all and subquery to do this.
sub_query = 'SELECT count(*) FROM `posts` WHERE `posts`.`user_id` = `users`.`id`'
User.where('num_posts IS NULL').update_all('num_posts = (#{sub_query})')
It will take only seconds instead of hours.
If so, you may not have to find a way to log something.
I'm using Rails 3 with a MySQL database, and I need to programmatically create a query like this:
select * from table where category_name like '%category_name_1%'
OR category_name like '%category_name_2%'
(...snip...)
OR category_name like '%category_name_n%'
Given the table size and the project scope (500 rows at most, I think), I feel that using something like thinking sphinx would be overkill.
I know I could simply do this by writing the query string directly, but wanted to know if there's an ActiveRecord way to do this. There's no mention of this on the official guide, and I've been googling for a long while now, just to end empty-handed :(
Also, is there a reason (maybe a Rails reason?) to not to include the OR clause?
Thanks!
Assuming you have an array names with category names:
Model.where( names.map{"category_name LIKE ?"}.join(" OR "),
*names.map{|n| "%#{n}%" } )
you should google first, there is already an answer.
Look here and then here
and you'll get something like this:
accounts = Account.arel_table
Account.where(accounts[:name].matches("%#{user_name}%").or(accounts[:name].matches("%#{user_name2}%")))
If you look at the guide, they have examples that can easily be modified to this:
Client.where("orders_count = ? OR locked = ?", params[:orders], false)
Mysql has a regexp function now that can clean things up a bit, assuming there's no regex metachars in your category names:
Table.where "category_name regexp '#{names.join('|')}'"
This is a request from my client to tweak an existing Perl script. However, it is the actual database structure on their end that confuses me.
The requirement looks pretty simple:
only pull records where _X begins with 1, 2, or 9.
However, the underlying database is not that simple, here is the guideline from their DBA:
"_X is a custom metadata field. The database stores this data in rows, not columns, within the customData table. In order to query the custom data table in an efficient manner you need to know the Field_ID for the custom field you get that from the fielddef table:
SELECT Field_ID FROM FieldDef WHERE Name = "_X";
This returns:
10012
"Now you can query CustomData. For example:
SELECT Record_ID FROM CustomData where Field_ID="10012" AND StringValue="2012-04";
He also suggests that in my case, probably it would be:
"SELECT Record_ID FROM CustomData where Field_ID="10012" AND (StringValue LIKE '1%' || StringValue LIKE '2%' || StringValue LIKE '9%')
The weird thing is that the existing Perl script doesn't contain anything like "Select Record_ID FROM" but all like "SELECT StringValue FROM".
So that is why I am very confused here: What is "store in rows, not in columns"? Why first query the Field_ID table then CustomData? I would not be able to communicate with any of them during this weekend but really wish to get some idea on the whole thing, hope experts can help me a little on sorting out the whole structure.
More info(Table schema):
http://pastebin.com/ZiDTCCC0
The existing perl script:(focus on lines 72-136)
http://pastebin.com/JHpikTeZ
Thanks in advance.
What they seem to be using is some kind of Entity-Attribute-Value model, with the entities stored as ints and explained in another table (FieldDef).
You explained pretty well how you queried it (although you can do it in one query, with a join or a subquery), and your problem seems to be that you don't know how the Perl script does it. Unfortunately, without us seeing the Perl script, we can't either :]
So, I need to search a real estate database for all homes belonging to realtors who are part of the same real estate agency as the current realtor. I'm currently doing this something like this:
$agency_data = $this->Realtor->find('all',array(
'conditions'=>
array(business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num'),
'recursive'=> -1
));
foreach($agency_data as $k=>$v){
foreach($v as $k=>$v1){
$agency_nums[] = $v1['num'];
}
}
$conditions = array(
'realtor_num'=>$agency_nums
);
It seems a bit crazy to me that I'm having to work so hard to break down the results of my first query, just to get a simple, one-dimensional array of ids that I can use to build a condition for my subsequent query. Am I doing this in an insanely roundabout way? Is there an easy way to write a single CakePHP query to communicate "select * from homes where realtor_num in (select num from realtors where business_name = 'n')"? If so, would it be any more efficient?
For sure it's complicated (in your way) :)
Depending from the results you can do following:
$agency_data = $this->Realtor->find('list',array(
'conditions'=>array('business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num', 'num'),
'recursive'=> -1
));
$agency_data; //this already contain array of id's
Method 2 - building a sub query there are 2 ways strict and not so strict :) The first one you can see here (search for Sub-queries).
The other option is to have following conditions parameter:
$this->Realtor->find('all', array('conditions'=>array('field in (select num from realtors where business_name like "'.$some_variable.'"))));
Of course be careful with the $some_variable in the sub-query. You shold escape it - use Sanitize class for example.
$agency_data = $this->Realtor->find('all',array(
'conditions'=>
array('business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num'),
'recursive'=> -1
));
$conditions = Set::extract("{n}.Realtor.num", $agency_data);
I would use something like Set::extract to grab the list of data you are looking for. The advantage of doing it this way is that you can reuse the same dataset in other places and save queries. You could also write the set::extract statement in this format:
$conditions = Set::extract("/Realtor/num", $agency_data);