Как избежать вставки повторяющихся записей при добавлении значений через отношение sqlalchemy?

предположим, что у нас есть две таблицы во многих отношениях, как показано ниже:

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:

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')

Как я могу сообщить отношениям, что они должны игнорировать дубликаты и не нарушать уникальное ограничение для таблицы языков? Конечно, я мог бы вставить каждый язык отдельно и проверить, существует ли запись в таблице заранее, но тогда большая часть преимуществ, предлагаемых отношениями sqlalchemy, исчезла.

2 ответов


в вики SQLAlchemy есть коллекция примеров, один из которых, как вы могли бы проверьте уникальность экземпляров.

примеры немного запутанные, хотя. В принципе, создайте classmethod get_unique в качестве альтернативного конструктора, который сначала проверит кэш сеанса, затем попробует запрос для существующих экземпляров, затем, наконец, создаст новый экземпляр. Тогда звоните Language.get_unique(id, name) вместо Language(id, name).

Я написал более подробная ответ в ответ на щедрость OP на другой вопрос.


Я бы предложил прочитать Прокси Ассоциации: Упрощение Объектов Ассоциации. В этом случае ваш код будет переведен на что-то вроде ниже:

# 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()