Simplify `Status.tagged_with_all` scope

Previously we were looping through all the supplied tag ids and
appending a new condition for each one so that the generated query was a
collection of AND'd EXISTS statements. Query times under this approach
are reasonable with small tag id counts, but grow as the tag id array
size grows.

The update uses a group by / having approach instead. Query times remain
fairly constant under this approach, with new IDs added to an `IN`
condition.
This commit is contained in:
Matt Jankowski 2024-03-27 10:08:31 -04:00
parent 2fe1b8d169
commit 65da3f259e
1 changed files with 1 additions and 7 deletions

View File

@ -113,13 +113,7 @@ class Status < ApplicationRecord
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).merge(Account.not_domain_blocked_by_account(account)) }
scope :tagged_with_all, lambda { |tag_ids|
Array(tag_ids).map(&:to_i).reduce(self) do |result, id|
result.where(<<~SQL.squish, tag_id: id)
EXISTS(SELECT 1 FROM statuses_tags WHERE statuses_tags.status_id = statuses.id AND statuses_tags.tag_id = :tag_id)
SQL
end
}
scope :tagged_with_all, ->(tag_ids) { joins(:tags).where(tags: { id: tag_ids }).group(:id).having(Arel.star.count.eq tag_ids.size) }
scope :tagged_with_none, lambda { |tag_ids|
where('NOT EXISTS (SELECT * FROM statuses_tags forbidden WHERE forbidden.status_id = statuses.id AND forbidden.tag_id IN (?))', tag_ids)
}