Выполнение SQL-запроса для получения коктейлей по идентификатору ингредиента в отношении многие ко многим - PullRequest
0 голосов
/ 13 января 2019

Мне нужна помощь в создании запроса SQL, который будет возвращать коктейль из базы данных, учитывая, что я предоставил все ингредиенты, входящие в этот коктейль

Так, например, я хочу, чтобы строка «Gin and Tonic» возвращалась, только если я предоставил правильные идентификаторы для Gin (id - 1) и Tonic (id - 2). Я только поставляю «Тоник», я не должен возвращаться обратно

Я использую SQLAlchemy и Flask, но мне все еще не удается обернуть голову, как запрос будет работать вообще

Вот так выглядит структура моей таблицы

+-------------------+
| Tables_in_my_database |
+-------------------+
| cocktails         |
| ing_in_cocktails  |
| ingredients       |
+-------------------+

Это мой коктейльный столик

+----+----------------+-------+---------+
| id | name           | glass | finish  |
+----+----------------+-------+---------+
|  1 | white russisan | rocks | stirred |
|  2 | gin and tonic  | rocks | stirred |
+----+----------------+-------+---------+

Это моя таблица ингредиентов

+----+---------+----------+
| id | name    | ing_type |
+----+---------+----------+
|  1 | vodka   | fruit    |
|  2 | kahluha | fruit    |
|  3 | gin     | fruit    |
|  4 | tonic   | fruit    |
+----+---------+----------+

А это моя реляционная таблица

+----+-------------+--------+
| id | cocktail_id | ing_id |
+----+-------------+--------+
|  1 |           1 |      1 |
|  2 |           1 |      2 |
|  3 |           2 |      3 |
|  4 |           2 |      4 |
+----+-------------+--------+

Вот соответствующие модели SQLAlchemy

class Cocktail(db.Model):
    __tablename__ = 'cocktails'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    glass = db.Column(db.String(20), nullable=False)
    finish = db.Column(db.String(20), nullable=True)
    ingredients = db.relationship(
        'Ingredient',
        secondary=ing_in_cocktails,
        backref=db.backref('cocktails', lazy='dynamic')
    )

class Ingredient(db.Model):
    __tablename__ = 'ingredients'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    ing_type = db.Column(db.String(20), nullable=False)


ing_in_cocktails = db.Table(
    'ing_in_cocktails',
    db.Column('id', db.Integer, primary_key=True),
    db.Column('cocktail_id', db.Integer, db.ForeignKey('cocktails.id')),
    db.Column('ing_id', db.Integer, db.ForeignKey('ingredients.id'))
)

Этот запрос помог мне пройти большую часть пути, но проблема в том, что он возвращает «Джин-тоник», если я предоставлю какой-либо ингредиент в коктейле

# problematic because this returns "Gin and Tonic," despite not passing
# all the ingredients
Cocktail.query.join(ing_in_cocktails).filter(ing_in_cocktails.columns.ing_id.in_([3]))

и приведенный выше запрос переводится в этот SQL

SELECT cocktails.id AS cocktails_id, cocktails.name AS cocktails_name, cocktails.glass AS cocktails_glass, cocktails.finish AS cocktails_finish
FROM cocktails INNER JOIN ing_in_cocktails ON cocktails.id = ing_in_cocktails.cocktail_id
WHERE ing_in_cocktails.ing_id IN (%(ing_id_1)s)

Ответы [ 4 ]

0 голосов
/ 15 января 2019

Для тех, кто ищет SQLAlchemy, чтобы сделать это

      query = db.session.query(Cocktail).filter(
          ~exists().where(
              and_(
                  CocktailIngredient.cocktail_id == Cocktail.id,
                  ~CocktailIngredient.ing_id.in_([15, 17])
              )
          )
      )
0 голосов
/ 14 января 2019

Вы можете попробовать следующий запрос с коррелированным подзапросом в HAVING предложении

select c.id
from cocktails c
join ing_in_cocktails ic on c.id = ic.cocktail_id 
where ing_id IN (1, 2)
group by c.id
having count(*) = (
  select count(*)
  from ing_in_cocktails
  where cocktail_id = c.id
)
0 голосов
/ 14 января 2019

То, что вы ищете, - это список коктейлей, в которых нет недостающих ингредиентов, то есть там есть NOT EXIST недостающих ингредиентов. Недостающие ингредиенты - это те, которые NOT IN ваш список доступных ингредиентов. С учетом этих наблюдений запрос, удовлетворяющий вашим требованиям, можно записать так:

select c.* from Cocktails c
 where not exists (select 1 from ing_in_cocktails r
                    where r.cocktail_id = c.id
                      and r.ing_id not in (3,4));

Если в списке ингредиентов 3, 4, вы можете сделать желаемый тоник и джин, если ингредиент 3 или 4 отсутствует в списке, то вы не можете.

0 голосов
/ 14 января 2019

Я не могу сказать вам, как написать это на python, но вот sql, что вам нужно, я надеюсь, это поможет

select
     ic.cocktail_id
from ing_in_cocktails ic
left join (
    -- here are you ingredients, that you have to pass as parameters.
    -- keep in mind, that they must be unique
    select 1 as ing_id
    union all select 2
    union all select 4
) ing_p on ic.ing_id = ing_p.ing_id
group by ic.cocktail_id
having count(*) = count(ing_p.ing_id) -- just need to check here, that amount of ingredients in cocktail is equal to ingredients, you passed as parameters
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...