具有多个多对多关系的 SQLAlchemy
Posted
技术标签:
【中文标题】具有多个多对多关系的 SQLAlchemy【英文标题】:SQLAlchemy with multiple Many to Many Relationships 【发布时间】:2018-03-28 18:01:50 【问题描述】:我想用 SQLAlchemy 为食谱创建一个数据库,但是我不确定我的方法是否正确。如何将数据插入 recipe_ingredient 表?
我的做法:
一个食谱有一个名字,可以有多种成分。一种成分由数量、单位和名称(成分表)组成,例如 500 毫升水。
餐桌食谱 - id,主键 - 姓名 (一个配方可以有多种成分,一种成分可以在多个配方中)
表配方_配料
外键(recipe.id) 外键(amounts.id) 外键(units.id) 外键(ingredients.id)餐桌金额
id,主键 金额(例如 500)表格单位
id,主键 单位(例如毫升)餐桌配料
id,主键 成分(例如水)代码:
recipe_ingredient = db.Table('recipe_ingredient',
db.Column('idrecipe', db.Integer, db.ForeignKey('recipe.id')),
db.Column('idingredient', db.Integer, db.ForeignKey('ingredients.id')),
db.Column('idunit', db.Integer, db.ForeignKey('units.id')),
db.Column('idamount', db.Integer, db.ForeignKey('amounts.id'))
)
class Recipe(db.Model):
id= db.Column(db.Integer, primary_key=True, autoincrement=True)
name= db.Column(db.VARCHAR(70), index=True)
ingredient= db.relationship("Ingredients", secondary=recipe_ingredient, backref='recipe')
amounts = db.relationship("Amounts", secondary=recipe_ingredient, backref='recipe')
units= db.relationship("Units", secondary=recipe_ingredient , backref='recipe')
class Ingredients(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
ingredient = db.Column(db.VARCHAR(200))
class Units(db.Model):
id= db.Column(db.Integer, primary_key=True, autoincrement=True)
unit= db.Column(db.VARCHAR(45), nullable=False)
class Amounts(db.Model):
id= db.Column(db.Integer, primary_key=True, autoincrement=True)
amount= db.Column(db.VARCHAR(45), nullable=False)
更新:
class RecipeIngredient(db.Model):
__tablename__ = 'recipe_ingredient'
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), primary_key=True)
ingredient_id = db.Column(db.Integer, db.ForeignKey('ingredient.id'), primary_key=True)
amount = db.Column(db.Integer, db.ForeignKey('amount.id'))
unit = db.Column(db.Integer, db.ForeignKey('unit.id'))
recipes = relationship("Recipe", back_populates="ingredients")
ingredients = relationship("Ingredient", back_populates="recipes")
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(70), index=True)
ingredients = relationship("RecipeIngredient", back_populates="recipes")
class Ingredient(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(200))
recipes = relationship("RecipeIngredient", back_populates="ingredients")
class Unit(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.VARCHAR(45), nullable=False)
class Amount(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
number = db.Column(db.VARCHAR(45), nullable=False)
我添加了以下对象
pizza = Recipe(name='Pizza')
flour = Ingredient(name='Flour')
water = Ingredient(name='Water')
g = Unit(name='g')
ml = Unit(name='ml')
a500 = Amount(number='500')
a100 = Amount(number='100')
r1= RecipeIngredient(recipe_id=pizza.id, ingredient_id=flour.id, amount=a500.id, unit=g.id)
r2= RecipeIngredient(recipe_id=pizza.id, ingredient_id=water.id, amount=a100.id, unit=ml.id)
pizza.ingredients 的结果:
[<RecipeIngredient 1, 1>, <RecipeIngredient 1, 3>]
我必须在该模型中添加什么才能获得成分的名称:
pizza.ingredients[0].name
【问题讨论】:
【参考方案1】:所以,您的关系定义和您的多对多 recipe_ingredient
表很好。你可以用你拥有的代码做你想做的事。您有一些文体问题使您的代码比应有的更难阅读。
工作原理
我们先来看看你拥有的功能:
Recipe
对象将具有ingredient
属性,其作用类似于列表。您可以将Ingredients
对象附加到它,当您调用它时,您将拥有Ingredients
的常规Python 列表:
# Make a cake,
cake = Recipe(name='Cake')
flour = Ingredients(ingredient='Flour')
eggs = Ingredients(ingredient='Eggs')
cake.ingredient.append(flour)
cake.ingredient.append(eggs)
for i in cake.ingredient:
print(i.ingredient)
因为您已经定义了Recipe.amount
的secondary
关系,其中secondary=recipe_ingredient
,SQLAlchemy 拥有为您管理多对多关系所需的所有信息。
Ingredients
对象将具有 recipe
属性,其作用类似于列表,并引用相同的关系:
# Find recipes using flour
for r in flour.recipe:
print(r.name)
您甚至可以将食谱添加到成分中,而不是将成分添加到食谱中,并且效果相同:
# Make cookies
cookies = Recipe(name='Cookies')
eggs.recipe.append(cookies)
for i in cookies.ingredient:
print(i.ingredient)
如何读取
您可能已经注意到,您命名事物的方式使它读起来有些笨拙。当任何属性引用一对多关系时,使用复数时会更清楚。例如,如果Recipe
中的成分关系实际上被称为ingredients
而不是ingredient
,它会读起来更好。这会让我们遍历cake.ingredients
。反过来也是如此:调用 backref recipes
而不是 recipe 会更清楚地表明 flour.recipes
指的是多个链接的食谱,其中 flour.recipe
可能有点误导。
关于你的对象是复数还是单数也有不一致的地方。 Recipe
是单数,但 Ingredients
是复数。老实说,关于哪种风格正确的意见并不普遍——我更喜欢对我的所有模型使用单数,Recipe
、Ingredient
、Amount
、Unit
——但这只是我。选择一种风格并坚持下去,而不是在它们之间切换。
最后,您的属性名称有点多余。 Ingredients.ingredient
有点多。 Ingredient.name
更有意义,也更清晰。
还有一件事
这里还有一件事 - 在我看来,您想要存储有关您的食谱/成分关系的其他信息,即成分的数量和单位。你可能想要 2 个鸡蛋或 500 克面粉,我猜这就是你的 Amounts
和 Units
桌子的用途。在这种情况下,这些附加信息是您关系的关键部分,您可以直接在关联表中对其进行报道,而不是尝试在单独的表中进行匹配。这需要更多的工作 - SQLAlchemy 文档更深入地介绍了如何使用 Association object 来管理这些额外的数据。可以说,使用这种模式将使您的代码更简洁,更易于长期管理。
更新
@user7055220 在我看来,您不需要单独的Amount
或Unit
表,因为这些值仅作为RecipeIngredient
关系的一部分才有意义。我会删除这些表,并将RecipeIngredient
中的amount
和unit
属性更改为直接存储单位/数量的String
和Integer
值:
class RecipeIngredient(db.Model):
__tablename__ = 'recipe_ingredient'
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), primary_key=True)
ingredient_id = db.Column(db.Integer, db.ForeignKey('ingredient.id'), primary_key=True)
amount = db.Column(db.Integer)
unit = db.Column(db.VARCHAR(45), nullable=False)
recipes = relationship("Recipe", back_populates="ingredients")
ingredients = relationship("Ingredient", back_populates="recipes")
在任何一种情况下,为了回答您关于如何访问成分值的问题 - 在您的示例中,pizza.ingredients
现在包含一个关联对象数组。该成分是该关联对象的子对象,可以通过其ingredients
属性访问。如果您进行了我上面建议的更改,您可以直接访问 unit
和 amount
值。访问该数据如下所示:
for i in pizza.ingredients:
print(i.amount) # This only works if you're storing the amount directly in the association proxy table as per my example above
print(i.unit) # Same goes for this
print(i.ingredients.name) # here, the ingredient is accessed via the "ingredients" attribute of the association object
或者,如果您只想使用您所询问的语法:
print(pizza.ingredients[0].ingredients.name)
关于命名的另一件事:注意这里你的关联对象中的 backref 对象被称为ingredients
,而它只映射到一个单一的成分,所以它应该是单数的——那么上面的例子就是pizza.ingredients[0].ingredient.name
,哪个听起来好一点
【讨论】:
非常感谢您提供的详细帮助。 Association 对象正是我所需要的。也感谢命名提示,我一定会坚持使用它们;)有一个自己的表有用吗? (我这样做是为了向用户展示已经可用的成分和单位)。但我不确定如何从配料中获取名称:pizza.ingredients[0].name 我已将模型和对象添加到上面的问题中。amount
作为 int 列是有意义的,但 unit
作为 varchar 没有意义,尤其是在没有任何检查的情况下。例如。对于盎司:oz
、oZ
、Oz
和 OZ
都是可能的 - 显然三个是错误的,但它们是可能的 - 并且架构应该阻止这种情况发生。
以上是关于具有多个多对多关系的 SQLAlchemy的主要内容,如果未能解决你的问题,请参考以下文章