【问题标题】:SQLAlchemy, prevent duplicate rowsSQLAlchemy,防止重复行
【发布时间】:2017-11-14 14:45:42
【问题描述】:

我想知道是否可以防止向数据库提交重复项。例如,假设有一个类如下

class Employee(Base):
   id = Column(Integer, primary_key=True)
   name = Column(String)

如果我要制作一系列这样的物品,

employee1 = Employee(name='bob')
employee2 = Employee(name='bob')

session.add_all([employee1, employee2])
session.commit()

我只想将一行添加到数据库中,并且employee1employee2 指向内存中的同一个对象(如果可能的话)。

SQLAlchemy 中是否有实现此功能的功能?或者我是否需要以编程方式确保不存在重复项?

【问题讨论】:

  • 听起来您的主键错误。如果这是您想要的,您应该将名称作为主键。尽管那时人们不能真正更改名称而不引起巨大的多表数据库更新,因为各处的所有外键都必须更改。另外,如果您尝试将名称更改为现有名称,会发生什么?您是否只想清除已经存在的行?
  • 按名称创建唯一索引,即使它不是主键,看起来也是强制名称唯一性的正确方法。
  • 为了防止提交重复,请按照@9000 所说的做。要让会话在第二个示例中只创建一次 bob,请参阅 unique object recipes
  • @nven - 除了阻止提交发生之外,数据库将如何解决问题?
  • 理想情况下,我希望 ORM 能够意识到存在重复条目,只提交一个,并且对重复项的所有引用都引用同一个对象

标签: python sqlalchemy


【解决方案1】:

另一种get_or_create() 解决方案:

from sqlalchemy.orm.exc import NoResultFound
# ...

def get_or_create(self, model, **kwargs):
    """
    Usage:
    class Employee(Base):
        __tablename__ = 'employee'
        id = Column(Integer, primary_key=True)
        name = Column(String, unique=True)

    get_or_create(Employee, name='bob')
    """
    instance = get_instance(model, **kwargs)
    if instance is None:
        instance = create_instance(model, **kwargs)
    return instance


def create_instance(model, **kwargs):
    """create instance"""
    try:
        instance = model(**kwargs)
        sess.add(instance)
        sess.flush()
    except Exception as msg:
        mtext = 'model:{}, args:{} => msg:{}'
        log.error(mtext.format(model, kwargs, msg))
        sess.rollback()
        raise(msg)
    return instance


def get_instance(self, model, **kwargs):
    """Return first instance found."""
    try:
        return sess.query(model).filter_by(**kwargs).first()
    except NoResultFound:
        return

【讨论】:

    【解决方案2】:

    至少有两种方法:

    • 数据库方法:创建相关主键;使用 SQLAlchemy,您可以定义例如基于您的简约示例name = Column('First Name', String(20), primary_key=True)
    • 编码方法:检查属性、属性集是否已经存在于表中,否则创建。查看相关代码示例here

    在性能方面,我相信数据库方法更好。这也是更有意义的一个。

    【讨论】:

      【解决方案3】:

      您可以创建一个类方法来获取或创建Employee -- 如果存在则获取它,否则创建:

      @classmethod
      def get_or_create(cls, name):
          exists = db.session.query(Employee.id).filter_by(name=name).scalar() is not None
          if exists:
              return db.session.query(Employee).filter_by(name=name).first()
          return cls(name=name)
      
      
      employee1 = Employee(name='bob')
      db.session.add(employee1)
      employee2 = Employee(name='bob')
      
      employee1 == employee2  # False
      
      
      bob1 = Employee.get_or_create(name='bob')
      if bob1 not in db.session:
          db.session.add(bob1)
      
      len(add_to_session) # 1
      
      bob2 = Employee.get_or_create(name='bob')
      if bob2 not in db.session:
          db.session.add(bob2)
      
      len(add_to_session) # 1
      
      bob1 == bob2  # True
      

      【讨论】:

      • 感谢stackoverflow.com/a/32952421/4386191 的快速exists 表达式。
      • 这很有趣。我希望有一种方法可以在提交时解决这个问题。我认为 bob1 需要先添加到会话中,然后get_or_create 才会将bob1 对象返回给bob2 = Employee.get_or_create(name='bob') 调用?在我的包的当前工作流程中,我首先生成这些类型的对象的大列表,并且在提交之前根本不与数据库交互
      • @nven 哎呀,你说得对,它必须被添加到会话中。我将此添加到示例中,并展示了一种可能适合您的模式。无需提交,只需在检查唯一性后将对象添加到会话中即可。
      猜你喜欢
      • 1970-01-01
      • 2020-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-01
      • 1970-01-01
      • 2010-11-09
      相关资源
      最近更新 更多