Flask SQLAlchemy多对多插入数据

106

我正在尝试在Flask-SQLAlchemy中创建多对多关系,但似乎不知道如何填写多对多标识数据库(many to many identifier database)。您能帮我理解我错在哪里以及应该如何实现吗?

class User(db.Model):
    __tablename__ = 'users'
    user_id = db.Column(db.Integer, primary_key=True)
    user_fistName = db.Column(db.String(64))
    user_lastName = db.Column(db.String(64))
    user_email = db.Column(db.String(128), unique=True)


class Class(db.Model):
    __tablename__ = 'classes'
    class_id = db.Column(db.Integer, primary_key=True)
    class_name = db.Column(db.String(128), unique=True)

然后是我的标识符数据库:

student_identifier = db.Table('student_identifier',
    db.Column('class_id', db.Integer, db.ForeignKey('classes.class_id')),
    db.Column('user_id', db.Integer, db.ForeignKey('users.user_id'))
)

目前当我尝试将数据插入到数据库时,它看起来是这样的。

# User
user1 = User(
            user_fistName='John',
            user_lastName='Doe',
            user_email='john@doe.es')

user2 = User(
            user_fistName='Jack',
            user_lastName='Doe',
            user_email='jack@doe.es')

user3 = User(
            user_fistName='Jane',
            user_lastName='Doe',
            user_email='jane@doe.es')

db.session.add_all([user1, user2, user3])
db.session.commit()

# Class
cl1 = Class(class_name='0A')
cl2 = Class(class_name='0B')
cl3 = Class(class_name='0C')
cl4 = Class(class_name='Math')
cl5 = Class(class_name='Spanish')
db.session.add_all([cl1, cl2, cl3, cl4, cl5])
db.session.commit()

我的问题是如何向多对多数据库添加内容, 因为我真的不能创建一个 'student_identifier' 对象?如果我可以的话,它可能会看起来像这样:

# Student Identifier
sti1  = StiClass(class_id=cl1.class_id, class_name=user1.user_id)
sti2  = StiClass(class_id=cl3.class_id, class_name=user1.user_id)
sti3  = StiClass(class_id=cl4.class_id, class_name=user1.user_id)
sti4  = StiClass(class_id=cl2.class_id, class_name=user2.user_id)
db.session.add_all([sti1, sti2, sti3, sti4])
db.session.commit()

我应该如何使用ORM插入多对多表?

4个回答

193

你不需要直接向你的关联表添加任何内容,SQLAlchemy会自动处理。这个说法基本上来自于SQLAlchemy文档

association_table = db.Table('association', db.Model.metadata,
    db.Column('left_id', db.Integer, db.ForeignKey('left.id')),
    db.Column('right_id', db.Integer, db.ForeignKey('right.id'))
)

class Parent(db.Model):
    __tablename__ = 'left'
    id = db.Column(db.Integer, primary_key=True)
    children = db.relationship("Child",
                    secondary=association_table)

class Child(db.Model):
    __tablename__ = 'right'
    id = db.Column(db.Integer, primary_key=True)


p = Parent()
c = Child()
p.children.append(c)
db.session.add(p)
db.session.commit()

因此,您的样例将会是这样的:

student_identifier = db.Table('student_identifier',
    db.Column('class_id', db.Integer, db.ForeignKey('classes.class_id')),
    db.Column('user_id', db.Integer, db.ForeignKey('students.user_id'))
)

class Student(db.Model):
    __tablename__ = 'students'
    user_id = db.Column(db.Integer, primary_key=True)
    user_fistName = db.Column(db.String(64))
    user_lastName = db.Column(db.String(64))
    user_email = db.Column(db.String(128), unique=True)


class Class(db.Model):
    __tablename__ = 'classes'
    class_id = db.Column(db.Integer, primary_key=True)
    class_name = db.Column(db.String(128), unique=True)
    students = db.relationship("Student",
                               secondary=student_identifier)

s = Student()
c = Class()
c.students.append(s)
db.session.add(c)
db.session.commit()

31
假设我的学生已经存在于表格中。如何将它们附加到班级中,而无需每次查询整个数据(仅使用外键)? - axwell
@axwell,如果您经常需要在更新时为班级查找学生,您可以像这样调整lazy='subquery'的关系:students = db.relationship("Student", secondary=student_identifier, lazy='subquery', backref=db.backref('classes', lazy=True)) - Andrii Danyleiko
1
@axwell 如果学生已经存在,您也可以像这样进行插入:session.execute( student_identifier.insert(), params={"class_id": class_id, "user_id": user_id}, ) - Edword
我们如何从存储库中访问学生标识符表(在这种情况下)? - undefined

25

首先,student_identifier 被定义为SQLAlchemy反射表,而不是数据库。

通常情况下,如果您正确设置了模型和反射表对象之间的所有关系,您只需要处理相关的模型(将模型对象附加到关系 InstrumentList 中)即可将数据插入反射表中。例如,@mehdi-sadeghi 上面提供的答案。

然而,如果您不想设置关系,确实有一种方法可以直接插入反射表。例如:

statement = student_identifier.insert().values(class_id=cl1.id, user_id=sti1.id)
db.session.execute(statement)
db.session.commit()

之后,您应该能够看到在student_identifier反射表中插入了一个多对多关系行。在执行每个SQL语句后不要忘记提交,因为它是在事务中完成的。

希望这有助于您采用另一种方法。


我该如何导入这个反射表或关联表? - jibin mathew
1
就像您处理类和其他符号一样,对@jibinmathew进行处理。 - Devy
@jibinmathew 我也遇到了导入关联表的问题,所以我在调用它的上面本地导入了这个表。 - Tri
1
@GofyandKitty 这可能是你的代码库中循环依赖/导入的指示器,因此你需要使用本地导入(提供命名空间)才能使其正常工作。 - Devy
1
非常感谢您分享这个解决方案。您真的救了我。 - Chidiebere
显示剩余2条评论

3

除了 Cowgill 的回答,你也可以使用 extend 一次添加多个条目:

class_ = db.session.query(Class).first()
new_students = db.session.query(Student).all()
class_.students.extend(new_students)
db.session.add(class_)
db.session.commit()

我需要重新分配所有的学生。在扩展之前如何重置学生?只需要使用class_.students = []然后再扩展吗? - Pynchia
我认为没有必要使用class_.students = []的方式。直接使用class_.students=new_students应该可以覆盖现有关系。 - NMO
我明白了,谢谢。但是现有的数据呢?我现在正在显式地删除它们,但如果我不这样做呢?ORM会自动删除吗? - Pynchia

0

我在我的模型上有这个

show = db.Table('Show',
  db.Column('artist_id', db.Integer, db.ForeignKey('artists.id'), primary_key = True),
  db.Column('venue_id', db.Integer, db.ForeignKey('venues.id'), primary_key = True),
  db.Column('start_time',db.DateTime, nullable = False )
)

class Venue(db.Model):
    __tablename__ = 'venues'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    city = db.Column(db.String(120))
    state = db.Column(db.String(120))
    address = db.Column(db.String(120))
    phone = db.Column(db.String(120))
    facebook_link = db.Column(db.String(120))
    image_link = db.Column(db.String(500))
    genres = db.Column(db.String())
    website_link = db.Column(db.String(120))
    seeking_talent = db.Column(db.Boolean, nullable = False, default= False)
    seeking_description = db.Column(db.String())
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    
    def __repr__(self):
            return f'<Venue ID: {self.id}, Name: {self.name}>'


class Artist(db.Model):
    __tablename__ = 'artists'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    city = db.Column(db.String(120))
    state = db.Column(db.String(120))
    phone = db.Column(db.String(120))
    genres = db.Column(db.String())
    facebook_link = db.Column(db.String(120))
    image_link = db.Column(db.String(500))
    website_link = db.Column(db.String(120))
    seeking_venue = db.Column(db.Boolean, nullable = False, default= False)
    seeking_description = db.Column(db.String())
    venues = db.relationship('Venue', secondary = show, backref = db.backref('artists', lazy = True))
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)

    def __repr__(self):
            return f'<Artist ID: {self.id}, Name: {self.name}>'

我在我的app.py中执行了以下插入操作(插入到关联表中)

 new_show = show.insert().values(
        artist_id = form.artist_id.data,
        venue_id = form.venue_id.data ,
        start_time = form.start_time.data
         )
      db.session.execute(new_show)
      db.session.commit()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接