Как связать строки в IN и на WHERE в SQL? - PullRequest
0 голосов
/ 18 февраля 2019

Я хочу сделать что-то вроде:

SELECT  "recipes"."id"
FROM "recipes"
    INNER JOIN "groups" ON "groups"."recipe_id" = "recipes"."id"
    INNER JOIN "steps" ON "steps"."group_id" = "groups"."id"
    INNER JOIN "steps_ingredients_memberships" ON "steps_ingredients_memberships"."step_id" = "steps"."id"
    INNER JOIN "ingredients" ON "ingredients"."id" =  "steps_ingredients_memberships"."ingredient_id"
 WHERE (ingredients.id IN (5, 6) AND ingredients.id IN (10, 11))
 LIMIT 10

Но этот запрос возвращает 0 строк ...
Я знаю, что этот запрос не может работать, но я не могу найти решение

Я хочу получить "Рецепты", которые содержат ингредиенты 5 OR 6 AND 10 OR 11. Думайте как:

5 = tomatoes
6 = big tomatoes
10 = potatoes
11 = big potatoes

Я хочу "Рецепты" с помидорами OR большойпомидоры AND картофель OR большой картофель.

Решение принято

Поскольку ингредиенты могут быть случайными, я написал сферу:

  scope :ingredients_filtering, lambda { |ingredients|
    return if ingredients.nil?

    queries = []
    ingredients.each do |ingredient|
      related_ids = ingredient.related_ids.uniq.join(', ')
      queries << "BOOL_OR(ingredients.id IN (#{related_ids}))"
    end
    group(:id).having(queries.join(' AND '))
  }

Спасибо вам всем!:)

Ответы [ 3 ]

0 голосов
/ 18 февраля 2019

Вы хотите агрегации.Сгруппируйте по рецепту и посмотрите, есть ли в нем нужные ингредиенты.

SELECT r.id 
FROM recipes r 
JOIN groups g ON g.recipe_id = r.id 
JOIN steps s ON s.group_id = g.id 
JOIN steps_ingredients_memberships sim ON sim.step_id = s.id 
JOIN ingredients i ON i.id = sim.ingredient_id 
GROUP BY r.id
HAVING BOOL_OR(i.id IN (5, 6)) AND BOOL_OR(i.id IN (10, 11));
0 голосов
/ 19 февраля 2019

Чтобы развернуть комментарий @ MrYoshiji Здесь мы можем сделать это более "безобидным" следующим образом:

ingredients_table = Ingredient.arel_table
ingredient_list1 = [5,6]
ingredient_list2 = [10,11]
condition = Arel::Nodes::NamedFunction.new(
              'BOOL_OR',[ingredients_table[:id].in(ingredient_list1)]
            ).and(
              Arel::Nodes::NamedFunction.new(
                'BOOL_OR',[ingredients_table[:id].in(ingredient_list2)]
              )
            )
recipes = Recipe.joins(groups: { 
                       steps: { 
                         steps_ingredients_memberships: :ingredients
                       }
                     })
        .group('recipes.id')
        .having(condition)

Это приведет к SQL, похожему на связанныйответ: (Предоставлено @ ThorstenKettner ), например,

SELECT recipes.*
FROM recipes 
  JOIN groups ON groups.recipe_id = recipes.id 
  JOIN steps ON steps.group_id = groups.id 
  JOIN steps_ingredients_memberships ON steps_ingredients_memberships.step_id = steps.id 
  JOIN ingredients ON ingredients.id = steps_ingredients_memberships.ingredient_id 
GROUP BY 
   recipes.id
HAVING 
  BOOL_OR(ingredients.id IN (5, 6)) AND 
  BOOL_OR(ingredients.id IN (10, 11))

Однако он позволяет гибко изменять ingredient_list1 и ingredient_list2 гораздо более эффективным образом, не заботясь о побеге илайк.

Кстати, чтобы ответить на ваш комментарий:

"Я поставил тег ruby-on-rails, потому что если есть решение в ruby-on-rails, то лучше, но яне думайте так ... "

arel может собрать любой запрос, который вы можете себе представить (он также может собрать неверный / фрагментированный SQL).Если он действителен, SQL ActiveRecord может запустить его, поэтому, если вы используете rails и у вас есть вопросы, обязательно включите тег , как вы это сделали.

Обновление (на основе вашего опубликованного решения), но с использованием arel вместо конкатенации строк

scope :ingredients_filtering, lambda { |ingredients|
  return unless ingredients
  ingredients_table = Ingredient.arel_table
  conditions = ingredients.map do |ingredient|
    Arel::Nodes::NamedFunction.new(
          'BOOL_OR',[ingredients_table[:id].in(ingredient.related_ids)]
        )
  end.reduce(&:and)
  group(:id).having(conditions)
}
0 голосов
/ 18 февраля 2019

используйте GROUP BY и HAVING:

SELECT  "recipes"."id"
FROM "recipes"
    INNER JOIN "groups" ON "groups"."recipe_id" = "recipes"."id"
    INNER JOIN "steps" ON "steps"."group_id" = "groups"."id"
    INNER JOIN "steps_ingredients_memberships" ON "steps_ingredients_memberships"."step_id" = "steps"."id"
    INNER JOIN "ingredients" ON "ingredients"."id" =  "steps_ingredients_memberships"."ingredient_id"
 GROUP BY "recipes"."id"
 HAVING COUNT(CASE WHEN ingredients.id IN (5, 6) THEN 1 END) > 0 
    AND COUNT(CASE WHEN ingredients.id IN (10, 11) THEN 1 END) > 0 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...