通过 sqlalchemy 关系添加值时如何避免插入重复条目?

Posted

技术标签:

【中文标题】通过 sqlalchemy 关系添加值时如何避免插入重复条目?【英文标题】:How to avoid inserting duplicate entries when adding values via a sqlalchemy relationship? 【发布时间】:2013-12-30 00:51:39 【问题描述】:

假设我们有两个多对多关系的表,如下所示:

class User(db.Model):
  __tablename__ = 'user'
  uid = db.Column(db.String(80), primary_key=True)
  languages = db.relationship('Language', lazy='dynamic',
                              secondary='user_language')

class UserLanguage(db.Model):
  __tablename__ = 'user_language'
  __tableargs__ = (db.UniqueConstraint('uid', 'lid', name='user_language_ff'),)

  id = db.Column(db.Integer, primary_key=True)
  uid = db.Column(db.String(80), db.ForeignKey('user.uid'))
  lid = db.Column(db.String(80), db.ForeignKey('language.lid'))

class Language(db.Model):
  lid = db.Column(db.String(80), primary_key=True)
  language_name = db.Column(db.String(30))

现在在 python shell 中:

In [4]: user = User.query.all()[0]

In [11]: user.languages = [Language('1', 'English')]

In [12]: db.session.commit()

In [13]: user2 = User.query.all()[1]

In [14]: user2.languages = [Language('1', 'English')]

In [15]: db.session.commit()

IntegrityError: (IntegrityError) column lid is not unique u'INSERT INTO language (lid, language_name) VALUES (?, ?)' ('1', 'English')

我怎样才能让关系知道它应该忽略重复项而不破坏 Language 表的唯一约束?当然,我可以分别插入每种语言,并事先检查表中是否已经存在该条目,但是这样就没有 sqlalchemy 关系提供的大部分好处了。

【问题讨论】:

【参考方案1】:

SQLAlchemy wiki 有一个 collection of examples,其中之一是您可能会如何 check uniqueness of instances。

这些例子有点复杂。基本上,创建一个类方法get_unique 作为备用构造函数,它将首先检查会话缓存,然后尝试查询现有实例,最后创建一个新实例。然后调用Language.get_unique(id, name) 而不是Language(id, name)

我写了一个more detailed answer 来回应 OP 对另一个问题的赏金。

【讨论】:

【参考方案2】:

我建议阅读Association Proxy: Simplifying Association Objects。在这种情况下,您的代码将转换为如下内容:

# NEW: need this function to auto-generate the PK for newly created Language
# here using uuid, but could be any generator
def _newid():
    import uuid
    return str(uuid.uuid4())

def _language_find_or_create(language_name):
    language = Language.query.filter_by(language_name=language_name).first()
    return language or Language(language_name=language_name)


class User(Base):
  __tablename__ = 'user'
  uid = Column(String(80), primary_key=True)
  languages = relationship('Language', lazy='dynamic',
                              secondary='user_language')

  # proxy the 'language_name' attribute from the 'languages' relationship
  langs = association_proxy('languages', 'language_name',
            creator=_language_find_or_create,
            )

class UserLanguage(Base):
  __tablename__ = 'user_language'
  __tableargs__ = (UniqueConstraint('uid', 'lid', name='user_language_ff'),)

  id = Column(Integer, primary_key=True)
  uid = Column(String(80), ForeignKey('user.uid'))
  lid = Column(String(80), ForeignKey('language.lid'))

class Language(Base):
  __tablename__ = 'language'
  # NEW: added a *default* here; replace with your implementation
  lid = Column(String(80), primary_key=True, default=_newid)
  language_name = Column(String(30))

# test code
user = User(uid="user-1")
# NEW: add languages using association_proxy property
user.langs.append("English")
user.langs.append("Spanish")
session.add(user)
session.commit()

user2 = User(uid="user-2")
user2.langs.append("English") # this will not create a new Language row...
user2.langs.append("German")
session.add(user2)
session.commit()

【讨论】:

AttributeError: type object 'Language' has no attribute 'query' OP 使用flask-sqlalchemy 将其添加到每个模型中。您可以将其替换为session.query(Language) 但是查询并不能解决 OP 的问题,在许多后续插入的情况下,通过将 user.languages 设置为 Language 对象的集合并提交 - 会显着减慢用于添加到许多用户的数据库中。是否有一种解决方案可以比每次检查它是否存在更有效地创建如果不存在(PostgresQL 中的 upsert)?如果列表像几百个字符串,也许在当前值的类旁边保留一个dict()

以上是关于通过 sqlalchemy 关系添加值时如何避免插入重复条目?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SQLAlchemy 声明性语法指定关系?

如何将自动过滤器添加到与 SQLAlchemy 的关系中?

如何在sqlalchemy before_insert事件中添加模型的关系实例?

在进行添加时如何通过使用 c++ 模板来避免临时对象? [关闭]

如何在 SQLAlchemy 中正确添加数据?

如何在SQLAlchemy模型的构造函数中通过关系存储数据?