SQLalchemy class 可以同时具有一对多和一对一关系吗?

Can a SQLalchemy class have both a one-to-many and a one-to-one relationship?

我正在尝试将歌曲和评级之间的一对一关系添加到我的烧瓶模型中,但是当我 运行 我的查询时出现错误。我已经按照 here 中的步骤进行操作,但是当涉及到 sqlalchemy 时,我仍然感到迷茫。歌曲已经具有有效的一对多关系,也许不能同时具有一对多和一对一关系。

from application import db

association_table = db.Table('association',
                             db.Column('songs_id', db.Integer,
                                       db.ForeignKey('songs.id')),
                             db.Column('genres_id', db.Integer,
                                       db.ForeignKey('genres.id'))
                             )


class Rating(db.Model):
    __tablename__ = 'songs_ratings'
    id = db.Column(db.Integer, primary_key=True)
    rating = db.Column(db.Numeric(precision=3, scale=2),
                       index=True, nullable=False)
    song_id = db.Column(db.Integer, db.ForeignKey('songs.id'))
    song = db.relationship("Song",  uselist=False,
                           back_populates="songs_ratings")

    def __repr__(self):
        return '{}'.format(self.rating)


class Song(db.Model):
    __tablename__ = 'songs'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), index=True, unique=True, nullable=False)
    artist = db.Column(db.String(30), primary_key=False,
                       unique=False, nullable=False)
    added = db.Column(db.Date, nullable=False)
    rating = db.relationship("Rating", back_populates="songs")
    genres = db.relationship(
        "Genre", secondary=association_table, backref=db.backref('songs'))

    def __repr__(self):
        return '{};{};{}'.format(self.title, self.artist, self.added)


class Genre(db.Model):
    __tablename__ = 'genres'
    id = db.Column(db.Integer, primary_key=True)
    category = db.Column(db.String(80), index=True,
                         unique=True, nullable=False)

    def __repr__(self):
        return '{}'.format(self.category)

错误

我在 运行 编译代码时遇到的错误。

---------------------------------------------------------------------------
InvalidRequestError                       Traceback (most recent call last)
<ipython-input-7-99ffacdf2d91> in <module>
----> 1 query = db.session.query(Rating, Song).filter(Rating.id==Song.id)
      2 df = pd.read_sql_query(query, db.engine)
      3 df

<string> in query(self, *entities, **kwargs)

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\session.py in query(self, *entities, **kwargs)
   2066         """
   2067 
-> 2068         return self._query_cls(entities, self, **kwargs)
   2069 
   2070     def _identity_lookup(

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\query.py in __init__(self, entities, session)
    173 
    174         self.session = session
--> 175         self._set_entities(entities)
    176 
    177     def _set_propagate_attrs(self, values):

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\query.py in _set_entities(self, entities)
    187                 post_inspect=True,
    188             )
--> 189             for ent in util.to_list(entities)
    190         ]
    191 

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\query.py in <listcomp>(.0)
    187                 post_inspect=True,
    188             )
--> 189             for ent in util.to_list(entities)
    190         ]
    191 

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\sql\coercions.py in expect(role, element, apply_propagate_attrs, argname, post_inspect, **kw)
    166                     if insp is not None:
    167                         if post_inspect:
--> 168                             insp._post_inspect
    169                         try:
    170                             resolved = insp.__clause_element__()

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\util\langhelpers.py in __get__(self, obj, cls)
   1158             if obj is None:
   1159                 return self
-> 1160             obj.__dict__[self.__name__] = result = self.fget(obj)
   1161             obj._memoized_keys |= {self.__name__}
   1162             return result

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\mapper.py in _post_inspect(self)
   2095 
   2096         """
-> 2097         self._check_configure()
   2098 
   2099     @HasMemoized.memoized_attribute

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\mapper.py in _check_configure(self)
   1872     def _check_configure(self):
   1873         if self.registry._new_mappers:
-> 1874             _configure_registries({self.registry}, cascade=True)
   1875 
   1876     def _post_configure_properties(self):

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\mapper.py in _configure_registries(registries, cascade)
   3382             # the order of mapper compilation
   3383 
-> 3384             _do_configure_registries(registries, cascade)
   3385         finally:
   3386             _already_compiling = False

~\AppData\Local\Programs\Python\Python37\lib\site-packages\sqlalchemy\orm\mapper.py in _do_configure_registries(registries, cascade)
   3417                 )
   3418                 e._configure_failed = mapper._configure_failed
-> 3419                 raise e
   3420 
   3421             if not mapper.configured:

InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class Rating->songs_ratings'. Original exception was: Mapper 'mapped class Song->songs' has no property 'songs_ratings'

代码有几个问题:

  1. 您的关系命名应与 back_populates 匹配。你有:
  • song / song_rating 在一段关系中,并且
  • rating / songs 在另一个规范中。

只需清理一下,如下所示:

class Rating(db.Model):
    # ...
    song_id = db.Column(db.Integer, db.ForeignKey("songs.id"))
    song = db.relationship("Song", back_populates="rating")

class Song(db.Model):
    # ...
    rating = db.relationship("Rating", uselist=False, back_populates="song")
  1. 上面也已经解决了你需要 uselist=False 的第二个关系,而不是第一个关系(虽然它并没有真正伤害),因为 ForeignKey 从 RatingSong.

但是,考虑到它是 1 对 1 的关系,并且没有歌曲就不能存在评级,您实际上可以重用您的原始模型并在 ForeignKey 上指定关系=21=] 列本身通过改变 Rating 模型如下:

class Rating(db.Model):
    __tablename__ = "songs_ratings"
    id = db.Column(db.Integer, db.ForeignKey("songs.id"), primary_key=True)  # <- added FK
    rating = db.Column(db.Numeric(precision=3, scale=2), index=True, nullable=False)

    song = db.relationship("Song", back_populates="rating")