如何获取至少包含所有标签的配置文件(has_and_belongs_to_many 关系)

How to get profiles containing at least all tags (has_and_belongs_to_many relation)

请帮助我理解,这是怎么回事?我的配置文件与标签有 has_and_belongs_to_many 关系。我希望能够过滤包含 至少所有标签 而不是其中任何一个的配置文件。我该怎么做?

我正在尝试这样做:

  Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')

但是我得到了非常奇怪的结果:

array = ['212', '213', '214']
=> ["212", "213", "214"]

profile.tag_ids
=> [212, 214, 213]

array = ['212', '214']
=> ["212", "214"]
irb(main):051:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
  SQL (0.7ms)  SELECT "profiles"."id" AS t0_r0, "profiles"."user_id" AS t0_r1, "profiles"."description" AS t0_r2, "profiles"."created_at" AS t0_r3, "profiles"."updated_at" AS t0_r4, "profiles"."first_name" AS t0_r5, "profiles"."last_name" AS t0_r6, "profiles"."date_of_birth" AS t0_r7, "profiles"."gender" AS t0_r8, "profiles"."short_time_price" AS t0_r9, "profiles"."long_time_price" AS t0_r10, "profiles"."city" AS t0_r11, "profiles"."line" AS t0_r12, "profiles"."instagram" AS t0_r13, "profiles"."facebook" AS t0_r14, "profiles"."whats_app" AS t0_r15, "profiles"."we_chat" AS t0_r16, "profiles"."other_contacts" AS t0_r17, "profiles"."geo_unit_id" AS t0_r18, "tags"."id" AS t1_r0, "tags"."body_en" AS t1_r1, "tags"."body_ru" AS t1_r2, "tags"."tags_group_id" AS t1_r3 FROM "profiles" LEFT OUTER JOIN "profiles_tags" ON "profiles_tags"."profile_id" = "profiles"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "profiles_tags"."tag_id" WHERE "tags"."id" IN (, ) GROUP BY profiles.id, tags.id HAVING (COUNT(tags) >= 2) LIMIT   [["id", 212], ["id", 214], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>

array = ['212', '213', '214']
=> ["212", "213", "214"]
irb(main):049:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
  SQL (0.8ms)  SELECT "profiles"."id" AS t0_r0, "profiles"."user_id" AS t0_r1, "profiles"."description" AS t0_r2, "profiles"."created_at" AS t0_r3, "profiles"."updated_at" AS t0_r4, "profiles"."first_name" AS t0_r5, "profiles"."last_name" AS t0_r6, "profiles"."date_of_birth" AS t0_r7, "profiles"."gender" AS t0_r8, "profiles"."short_time_price" AS t0_r9, "profiles"."long_time_price" AS t0_r10, "profiles"."city" AS t0_r11, "profiles"."line" AS t0_r12, "profiles"."instagram" AS t0_r13, "profiles"."facebook" AS t0_r14, "profiles"."whats_app" AS t0_r15, "profiles"."we_chat" AS t0_r16, "profiles"."other_contacts" AS t0_r17, "profiles"."geo_unit_id" AS t0_r18, "tags"."id" AS t1_r0, "tags"."body_en" AS t1_r1, "tags"."body_ru" AS t1_r2, "tags"."tags_group_id" AS t1_r3 FROM "profiles" LEFT OUTER JOIN "profiles_tags" ON "profiles_tags"."profile_id" = "profiles"."id" LEFT OUTER JOIN "tags" ON "tags"."id" = "profiles_tags"."tag_id" WHERE "tags"."id" IN (, , ) GROUP BY profiles.id, tags.id HAVING (COUNT(tags) >= 3) LIMIT   [["id", 212], ["id", 213], ["id", 214], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>

array = ['212', '213']
=> ["212", "213"]
irb(main):047:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>

array = ['214', '213']
=> ["214", "213"]
irb(main):039:0> Profile.includes(:tags).where(tags: {id: array }).having('COUNT(tags) >= ?', array.count).group('profiles.id, tags.id')
=> #<ActiveRecord::Relation [#<Profile id: 84, ...>]>

怎么可能?也许还有另一种获取配置文件的方法,包含所有提供的标签?

db 模式中间 table 这里:

  create_table "profiles_tags", force: :cascade do |t|
    t.bigint "profile_id"
    t.bigint "tag_id"
    t.index ["profile_id"], name: "index_profiles_tags_on_profile_id"
    t.index ["tag_id"], name: "index_profiles_tags_on_tag_id"
  end

与关系的迁移在这里:

# frozen_string_literal: true

class CreateProfilesTags < ActiveRecord::Migration[6.0]
  def change
    create_table :profiles_tags do |t|
      t.belongs_to :profile
      t.belongs_to :tag
    end
  end
end

你快到了:

Profile.joins(:tags)                  # <= use `joins` instead of `inclused`
       .where(tags: {id: array })
       .having('COUNT(tags) >= ?', array.count)
       .group('profiles.id')          # <= Do not use anything tags related here

请务必注意 includesjoins 之间的区别。 joins 总是进行数据库连接 included 只在特定条件下进行连接,有时它只是进行两个查询。当 Rails 必须进行数据库连接才能使查询正常工作时使用 joins,当您想修复 n+1 问题时使用 includes(请参阅 Rails :include vs. :joins)。

当您同时需要数据库连接和 n+1 问题的修复时,然后 运行 将连接查询作为子查询:

# a new scope in the mode
scope :when_tags, ->(tags) {
  joins(:tags)
    .where(tags: {id: tags })
    .having('COUNT(tags) >= ?', tags.count)
    .group('profiles.id')
}

# the actual nested query with includes:
Profile.where(id: Profile.with_tags(array)).include(:tags)