Is there a way to compare an associated belongs_to record with an existing record and not hit the database? For example, I have a User, and want to see if an Account belongs to the User. The simple way is account.user == user, but that loads the account's user from the database and then compares it will user. The DB call is:
SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
Alternately, although it's undocumented, it looks like I can do user.accounts.include?(account), but that calls:
SELECT 1 AS one FROM `accounts` WHERE `accounts `.`user_id` = 1 AND `accounts`.`id` = 1 LIMIT 1
Of course, I could just do account.user_id == user.id, but that doesn't feel very "railsy".
I don't think it's possible because Rails relations are lazy loaded by default; the accounts table row in the database only contains the user_id column and that all what it knows about the user but whenever you need to call account.user a separate database query will be made to get other User columns than id to compare that with different User objects.
Related
I have the following statement:
Customer.where(city_id: cities)
which results in the following SQL statement:
SELECT customers.* FROM customers WHERE customers.city_id IN (SELECT cities.id FROM cities...
Is this intended behavior? Is it documented somewhere? I will not use the Rails code above and use one of the followings instead:
Customer.where(city_id: cities.pluck(:id))
or
Customer.where(city: cities)
which results in the exact same SQL statement.
The AREL querying library allows you to pass in ActiveRecord objects as a short-cut. It'll then pass their primary key attributes into the SQL it uses to contact the database.
When looking for multiple objects, the AREL library will attempt to find the information in as few database round-trips as possible. It does this by holding the query you're making as a set of conditions, until it's time to retrieve the objects.
This way would be inefficient:
users = User.where(age: 30).all
# ^^^ get all these users from the database
memberships = Membership.where(user_id: users)
# ^^^^^ This will pass in each of the ids as a condition
Basically, this way would issue two SQL statements:
select * from users where age = 30;
select * from memberships where user_id in (1, 2, 3);
Each of these involves a call on a network port between applications and the data to then be passsed back across that same port.
This would be more efficient:
users = User.where(age: 30)
# This is still a query object, it hasn't asked the database for the users yet.
memberships = Membership.where(user_id: users)
# Note: this line is the same, but users is an AREL query, not an array of users
It will instead build a single, nested query so it only has to make a round-trip to the database once.
select * from memberships
where user_id in (
select id from users where age = 30
);
So, yes, it's expected behaviour. It's a bit of Rails magic, it's designed to improve your application's performance without you having to know about how it works.
There's also some cool optimisations, like if you call first or last instead of all, it will only retrieve one record.
User.where(name: 'bob').all
# SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob'
User.where(name: 'bob').first
# SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' AND ROWNUM <= 1
Or if you set an order, and call last, it will reverse the order then only grab the last one in the list (instead of grabbing all the records and only giving you the last one).
User.where(name: 'bob').order(:login).first
# SELECT * FROM (SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' ORDER BY login) WHERE ROWNUM <= 1
User.where(name: 'bob').order(:login).first
# SELECT * FROM (SELECT "USERS".* FROM "USERS" WHERE "USERS"."NAME" = 'bob' ORDER BY login DESC) WHERE ROWNUM <= 1
# Notice, login DESC
Why does it work?
Something deep in the ActiveRecord query builder is smart enough to see that if you pass an array or a query/criteria, it needs to build an IN clause.
Is this documented anywhere?
Yes, http://guides.rubyonrails.org/active_record_querying.html#hash-conditions
2.3.3 Subset conditions
If you want to find records using the IN expression you can pass an array to the conditions hash:
Client.where(orders_count: [1,3,5])
This code will generate SQL like this:
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
I want to expand this question.
order by foreign key in activerecord
I'm trying to order a set of records based on a value in a really large table.
When I use join, it brings all the "other" records data into the objects.. As join should..
#table users 30+ columns
#table bids 5 columns
record = Bid.find(:all,:joins=>:users, :order=>'users.ranking DESC' ).first
Now record holds 35 fields..
Is there a way to do this without the join?
Here's my thinking..
With the join I get this query
SELECT * FROM "bids"
left join users on runner_id = users.id
ORDER BY ranking LIMIT 1
Now I can add a select to the code so I don't get the full user table, but putting a select in a scope is dangerous IMHO.
When I write sql by hand.
SELECT * FROM bids
order by (select users.ranking from users where users.id = runner_id) DESC
limit 1
I believe this is a faster query, based on the "explain" it seems simpler.
More important than speed though is that the second method doesn't have the 30 extra fields.
If I build in a custom select inside the scope, it could explode other searches on the object if they too have custom selects (there can be only one)
What you would like to achieve in active record writing is something along
SELECT b.* from bids b inner join users u on u.id=b.user_id order by u.ranking desc
In active record i would write such as:
Bids.joins("inner join users u on bids.user_id=u.id").order("u.ranking desc")
I think it's the only to make a join without fetching all attributes from the user models.
I'm trying to find the best way to count the number of Users who have one (or many) instances of a has_many relation.
For example, User has_many :bank_accounts and :credit_accounts (and a few other relations). I want to find the number of unique Users who have at least one bank_account and at least one credit_account, and ideally implement this inside of a scope so I can run where queries on it.
At the moment I'm implementing it (poorly) using the following code:
(BankAccount.select(:user_id).uniq + CreditAccount.select(:user_id) + ...).uniq.count
I've played around a lot with some joins, however I'm not getting any results. For example, I've toyed around a lot with different forms of User.joins(:bank_accounts, :credit_accounts).uniq('users.id').count however I don't appear to be getting any results.
Any help would be greatly appreciated, thanks!
If you are fine with using normal sql. You can use the below query
select distinct(user_id) from
(select user_id from bank_accounts union select user_id from credit_accounts) a;
I am not sure if a rails way exists for this.
In this case all we need is an INNER JOIN of users with credit_accounts and bank_accounts.
User.joins(:credit_accounts, :bank_accounts).uniq.count
The above query works for me. The sql generated by this query is below
"SELECT DISTINCT COUNT(DISTINCT `users`.`id`) FROM `users`.* FROM `users` INNER JOIN `credit_accounts` ON `credit_accounts`.`user_id` = `users`.`id` INNER JOIN `bank_accounts` ON `bank_accounts`.`user_id` = `users`.`id`"
We have 2 tables: users and statuses
The status table has a user_id, status and occured_on. The status is either 'removed' or 'added' and occured_on is the date the user was removed or added.
I need the current added users. That is, all the (distinct) users whose newest status record is 'added'.
I'm using Rails, and have tried:
User
.joins(:statuses)
.where('statuses.status = ?', 'added')
.order('statuses.occured_on DESC')
.uniq
Which translates to the SQL:
SELECT DISTINCT users.*
FROM users
INNER JOIN statuses
ON statuses.user_id = users.id
WHERE statuses.status = 'added'
ORDER BY statuses.occured_on DESC
That gives me the error:
PG::Error: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...statuses.status = 'added') ORDER BY statuses.oc...
I'd be happy knowing either the Rails code that would work or the straight SQL.
Also, I'd prefer no sub-selects if possible.
Concider the following database schema change:
StatusTable:
StatusId
Status
UserId
ActiveFrom
ActiveTo
Afterwards you can add additional checks such as:
CONSTRAINT chk_from_to CHECK (ActiveFrom <= ActiveTo)
Then your query would look something like:
SELECT users.*
FROM users
JOIN statuses ON UserId = users.user_id AND ActiveFrom < CURRENT_TIMESTAMP AND ActiveTo > CURRENT_TIMESTAMP
WHERE statuses.Status = 'active'
With such structure you might need to change the way you change statuses, but from my own experience, this structure is much more flexible, and easier to query.
SELECT * FROM users INNER JOIN statuses ON users.id=statuses.user_id WHERE statuses.status='added' ORDER BY statuses.occured_on
After clarification, I don't think the schema is well designed for your goal. Can you clarify why you want the status change history contained in that table? My general approach to this would be that active users should be contained in a table called projects_users, containing project_id, user_id. When they are "removed" they should be removed from that table. Logs of the actions - adding and remove users from projects - should be stored in a separate table.
There's no good way that I'm aware of to write this query given your current design. Even if you fixed the errors, this runs error free in MySQL (which is exactly what you have)
SELECT DISTINCT `users`.* FROM `users`
INNER JOIN `projects_users`
ON `users`.`id`=`projects_users`.`user_id`
WHERE `status`='added'
ORDER BY `projects_users`.`occured_on` DESC
it still won't get you the correct results. The ORDER BY clause will just get you the most recent change to "added", it won't guarantee there is not a more recent "removed" action. To do that you'd need to compare the date of each most recent added record to the date of the most recent removed record, for each user, a nightmare.
I have two models/tables A and B. I'd like to perform an Active Record query where the results include columns from both tables. I tried inner joins as they sounded like they combine columns from both tables. However trying the Active Record joins finder method returns results from only the first table.
What Active Record queries include columns from two tables in the results? Perhaps the includes finder method could help.
Edit: think of the two tables as ForumThreads and Posts. Each forum thread has multiple posts. I'd like the rows in the query results to contain information for each post and information for the forum thread (for example the thread title).
This question might have answered my question: Rails Joins and include columns from joins table
Joins performs an inner join, but will not return the data until you ask for it.
User.where(:id => 1).joins(:client_applications)
User Load (0.2ms) SELECT "users".* FROM "users" INNER JOIN "client_applications" ON "client_applications"."user_id" = "users"."id" WHERE "users"."id" = 1
Includes will execute two queries (using where in) and cache the associated data (Eager Loading)
User.where(:id => 1).includes(:client_applications)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
ClientApplication Load (13.6ms) SELECT "client_applications".* FROM "client_applications" WHERE "client_applications"."user_id" IN (1)