Alias foreign key and enumerate columns to push condition into subselect#1
Conversation
|
Woo! what an honor to receive a pull request from you @bensheldon 😃 ! Let's see if I understand... Apparently the thing that is doing the magic is the For example... Do you know if in your gist, something like this could work?... |
|
💖 I recommend your blog posts about Lateral Joins all the time. It's a fantastic resource! THANK YOU! This was me on Reddit 😊 https://www.reddit.com/r/rails/comments/kmofhp/comment/ghnf0hy/
Unfortunately that results in the error: ERROR: column reference "user_id" is ambiguous
LINE 14: WHERE "newsletter_deliveries"."user_id" IN (1, 2)The error happens because the query in the sub-select has two I have yet to find a working strategy that allows for not enumerating the columns. But I'm chatting with some folks about it 🤞🏻 |
|
I try to copy what you did in your original pull request https://github.com/bensheldon/open311status/pull/98/files and I think that is working. example "In a has many association" do
class User < ActiveRecord::Base
has_many :posts
has_many :last_posts, -> { last_n_per_user(3) }, class_name: "Post"
end
class Post < ActiveRecord::Base
scope :last_n_per_user, ->(n) {
query = select('subquery.*').from('(SELECT *, id AS user_id FROM users) AS posts')
join_sql = <<~SQL
JOIN LATERAL (
SELECT * FROM posts AS sub_posts
WHERE sub_posts.user_id = posts.user_id
ORDER BY sub_posts.id DESC LIMIT :limit
) AS subquery ON TRUE
SQL
query.joins(sanitize_sql_array([join_sql, { limit: n }]))
}
end
users = User.preload(:last_posts).limit(5).explain
puts users
#pp users.map { |user| [user.name, user.last_posts.map(&:id)] }
endThis is the output... Can you help me see if this is right/better? |
|
@bhserna ooh, nice! Yes, that does look equivalent. Sidenote: I just noticed in this project that there isn't multicolumn index on So yes, I think your query is probably better than mine from a readability perspective, and equivalent in performance (maybe better / more reliable) 🙌🏻 So yes, let's go with that. Counterpoint: I'm working on turning this into an extension for Active Record (gem here, though I haven't published to Rubygems yet). The one thing about your proposal I need to look into, from an Active Record perspective (rather than raw SQL) is how to rewrite all of the scopes in the lateral to use the table alias. |
|
@bensheldon after seeing what you are trying to do in your gem I think that is better to use the approach that you just shared in the pull request. I couldn't find a way to be able to pass a scope in a lambda, like in this example... class User < ActiveRecord::Base
# like this
has_one_of_many :last_post, -> { order("created_at DESC") }, class_name: "Post"
end... because if I understand it right, you need to preserve the table name without alias to allow the user of the gem to build scopes without thinking in the internal representation. So, I think that I will merge your pull request as is... Thank you! Also, I will try to update the post or share in some way what you found... jeje but it won't be easy to explain 😅 |
Hi! Thank you so much for blogging about N+1 queries and popularizing lateral joins in associations.
So I found a problem with the query: the outermost condition of the association (e.g.
WHERE user_id IN ($1, ...)) doesn't get pushed down in the current query. This leads to the lateral join being applied to all records before the condition is applied.I have a solution though. I only modified one of the queries in the PR to demonstrate it.
(Also, I did do some slight Arel-izing, but I'm happy to rewrite it with strings if that's clearer. Mainly here I just wanted to show you what I found.)
Here's some notes from my own application with
EXPLAIN ANALYZE: https://gist.github.com/bensheldon/4054feed291e8ae7f54d40b14ad7ba79