简体   繁体   中英

How to select data records from join table with few options?

I'm writing a simple online shop on ruby and have a problem with items filtering. I'm trying to filter items by few options (eg 'color': 'red' and 'brand': 'zara').

I expect to see only items that have both 'red' and 'zara' options.

Everything is OK when I filter by one option. But with few options, it shows all items that have at least one of the options.

My filtering scope in Item model:

scope :by_options,
    ->(options) { joins(:items_options).where(items_options: { option_id: options }).distinct }

Items controller:

class ItemsController < ApplicationController
  def index
    @items = Item.all

    filtering_params(params).each do |key, value|
      @items = @items.public_send("by_#{key}", value) if value.present?
    end
  end

  private
    def filtering_params(params)
      params.slice(:category, :options)
    end
  end

Generated query:

SELECT DISTINCT "items".* FROM "items" INNER JOIN "items_options" ON "items_options"."item_id" = "items"."id" WHERE "items_options"."option_id" IN (?, ?)  [["option_id", 1], ["option_id", 6]]

I also tried to filter by options twice, but in this case @items are empty (6 and 1 are just examples, because I have item that have both of this options:

@items = Item.by_options(6).by_options(1)

Generated query:

SELECT DISTINCT "items".* FROM "items" INNER JOIN "items_options" ON "items_options"."item_id" = "items"."id" WHERE "items_options"."option_id" = ? AND "items_options"."option_id" = ?  [["option_id", 6], ["option_id", 1]]

DB schema:

create_table "items", force: :cascade do |t|
  t.string "name", null: false
  t.decimal "price", null: false
  t.integer "available_count", null: false
  t.integer "category_id"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
  t.index ["category_id"], name: "index_items_on_category_id"
end

create_table "items_options", id: false, force: :cascade do |t|
  t.integer "item_id", null: false
  t.integer "option_id", null: false
end

create_table "options", force: :cascade do |t|
  t.string "name", null: false
  t.integer "filter_id"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
  t.index ["filter_id"], name: "index_options_on_filter_id"
end

You can try:

scope :by_options,
    ->(options) { 
          self.joins(:items_options)
              .having('SUM(items_options.option_id in (?)) = ?', options, options.size)
     }

You're basically saying: "Give me all items that have all the items_options.option_id I specified".

This is a little bit dirty, and I haven't tested it, but from the top of my head it should do the trick.

Otherwise, would you mind linking an SQL schema creation statement to your post so we could run the queries more easily?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM