SQLAlchemy:创建多对多并填充关联

Posted

技术标签:

【中文标题】SQLAlchemy:创建多对多并填充关联【英文标题】:SQLAlchemy: create many-to-many and populate association 【发布时间】:2022-01-13 14:15:15 【问题描述】:

我的想法如下:

有一个主表 (Documents),其中包含一些文本,例如博客文章。每个文档都有一个唯一的 ID text_id。 有一个辅助表 (Links) 存储这些帖子中出现的唯一 URL。每个网址都有一个唯一的 ID url_id。 这些由关联表 (Association) 绑定,该关联表将文本的 id 映射到域的 id。

我希望能够获取帖子,从中收集网址,然后:

Documents 中创建新记录 如果它包含新的 url - 将它们添加到 Links 并通过 Association 与文档相关联 如果文档包含已有的 url - 仅在新文档和这些文档之间创建关联。

对于初学者,我创建了三个类,例如here:

class Association(Base):
    __tablename__ = 'association'
    text_id = Column('text_id', Integer, ForeignKey('left.text_id'), primary_key=True)
    url_id = Column('url_id', Integer, ForeignKey('right.url_id'), primary_key = True)
    child = relationship("Links", back_populates='parents')
    parent = relationship("Documents", back_populates='children')

class Documents(Base):
    __tablename__ = 'left'
    text_id = Column(Integer, primary_key=True, unique=True)
    text = Column(Text)
    children = relationship("Association", back_populates='parent')

class Links(Base):
    __tablename__ = 'right'
    url_id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
    url = Column(Text, unique=True)
    parents = relationship('Association', back_populates = 'child')

Base.metadata.create_all(engine)

然后我正在尝试加载数据:

data = [
    'id':1, 'text':'sometext', 'url':'facebook.com',
    'id':2, 'text':'sometext', 'url':'twitter.com',
    'id':3, 'text':'sometext', 'url':'twitter.com'
]

for row in data:
    d = Document(text_id = row['id'])
    a = Association()
    a.child = Links(url = row['url'])
    d.children.append(a)
    session.add(d)
session.commit()

这会导致错误:

Traceback (most recent call last):
  File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3444, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-13-325b1cd57576>", line 5, in <module>
    p.children.append(a)
  File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 1240, in __getattr__
    return self._fallback_getattr(key)
  File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 1214, in _fallback_getattr
    raise AttributeError(key)
AttributeError: append

我真的不明白为什么,因为我似乎按照官方文档的建议做了所有事情。

另一方面,即使这样可行,我怀疑通过 p.children.append(a) 附加一个已经存在的 url 可能会导致错误,因为它实际上会尝试创建一个副本,而 Links 不允许这样做。

如果重要的话,我正在使用 mysql 和 MariaDB。

也许我为这项工作选择了错误的工具 - 如果您能提出替代方案,我将不胜感激。

UPD:我无法插入,因为我使用 automap_base() 而不是 declarative_base() 实例化了一个基。现在我可以追加,但是,重复的条目确实是个问题:

sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry 'twitter.com' for key 'url'")
[SQL: INSERT INTO `right` (url) VALUES (%(url)s)]
[parameters: 'url': 'twitter.com']
(Background on this error at: https://sqlalche.me/e/14/gkpj)

【问题讨论】:

【参考方案1】:

首先,如果您使用正确的域名而不是:rightleftchildchildren,将更容易调试。我知道这是文档的副本,但是文档是通用的,而您的案例是特定的。您的代码将更具可读性。

为避免重复,您应在插入该记录之前检查是否已存在(Documents 具有唯一的 text_idLinks 具有唯一的 url)。

for row in data:
    d = session.query(Document).filter_by(text_id=row['id']).first()
    if not d:
        d = Document(text_id=row['id'])
    link = session.query(Links).filter_by(url=row['url']).first():
    if not link:
        link = Links(url=row['url'])
    a = Association(child=link)
    d.children.append(a)
    session.add(d)
    session.flush()
session.commit()

【讨论】:

非常感谢您的解决方案!是的,下次我会更加注意命名:)

以上是关于SQLAlchemy:创建多对多并填充关联的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy 使用关联配置与自我的多对多关系

Flask/SQLAlchemy - 多对多关系的关联模型和关联表之间的区别?

使用 ID 而不是对象填充 SQLAlchemy 多对多关系

Flask SQLAlchemy 多对多关联对象错误

通过 SQLAlchemy 选择多个多对多关系

SQLAlchemy 通过关联对象声明性多对多自联接