我想从数据库中获得一个对象,如果它已经存在(基于提供的参数)或创建它,如果它不存在。

Django的get_or_create(或source)可以做到这一点。在SQLAlchemy中是否有等价的快捷方式?

我现在明确地像这样写出来:

def get_or_create_instrument(session, serial_number):
    instrument = session.query(Instrument).filter_by(serial_number=serial_number).first()
    if instrument:
        return instrument
    else:
        instrument = Instrument(serial_number)
        session.add(instrument)
        return instrument

当前回答

有一个Python包包含@erik的解决方案以及一个版本的update_or_create()。https://github.com/enricobarzetti/sqlalchemy_get_or_create

其他回答

基本上就是这么做的,没有捷径可走。

当然,你可以把它概括为:

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).one_or_none()
    if instance:
        return instance, False
    else:
        params = {k: v for k, v in kwargs.items() if not isinstance(v, ClauseElement)}
        params.update(defaults or {})
        instance = model(**params)
        try:
            session.add(instance)
            session.commit()
        except Exception:  # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
            session.rollback()
            instance = session.query(model).filter_by(**kwargs).one()
            return instance, False
        else:
            return instance, True

2020年更新(Python 3.9+ ONLY)

下面是一个简洁的版本,使用Python 3.9的新字典联合运算符(|=)

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).one_or_none()
    if instance:
        return instance, False
    else:
        kwargs |= defaults or {}
        instance = model(**kwargs)
        try:
            session.add(instance)
            session.commit()
        except Exception:  # The actual exception depends on the specific database so we catch all exceptions. This is similar to the official documentation: https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html
            session.rollback()
            instance = session.query(model).filter_by(**kwargs).one()
            return instance, False
        else:
            return instance, True

注意:

类似于Django版本,这将捕获重复的关键约束和类似的错误。如果你的get或create不能保证返回一个结果,它仍然会导致竞争条件。

为了缓解这个问题,你需要在session.commit()之后添加另一个one_or_none()样式的获取。这仍然不能100%保证不出现竞争条件,除非您还使用with_for_update()或可序列化事务模式。

我稍微简化了一下@凯文。避免将整个函数包装在if/else语句中的解决方案。这样就只有一次返回,我觉得更干净:

def get_or_create(session, model, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()

    if not instance:
        instance = model(**kwargs)
        session.add(instance)

    return instance

有一个Python包包含@erik的解决方案以及一个版本的update_or_create()。https://github.com/enricobarzetti/sqlalchemy_get_or_create

遵循@WoLpH的解决方案,这是适用于我的代码(简单版本):

def get_or_create(session, model, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        instance = model(**kwargs)
        session.add(instance)
        session.commit()
        return instance

这样,我就能够get_or_create我的模型的任何对象。

假设我的模型对象是:

class Country(Base):
    __tablename__ = 'countries'
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True)

为了获得或创建我的对象,我写:

myCountry = get_or_create(session, Country, name=countryName)

根据所采用的隔离级别,上述解决方案都不起作用。 我发现的最好的解决方案是一个RAW SQL在以下形式:

INSERT INTO table(f1, f2, unique_f3) 
SELECT 'v1', 'v2', 'v3' 
WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')

无论隔离级别和并行度如何,这都是事务安全的。

注意:为了提高效率,明智的做法是为唯一的列使用INDEX。