SQLAlchemy有相当于Django的get_or_create吗?
我想从数据库中获取一个对象,如果它已经存在(基于提供的参数),或者创build它,如果它不存在。
Django的get_or_create
(或源 )这样做。 在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
这基本上是做到这一点,没有捷径现成的AFAIK。
你可以概括它的过程:
def get_or_create(session, model, defaults=None, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: return instance, False else: params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement)) params.update(defaults or {}) instance = model(**params) session.add(instance) return instance, True
在@WoLpH的解决scheme之后,这是我工作的代码(简单版本):
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)
要获得或创build我的对象,我写:
myCountry = get_or_create(session, Country, name=countryName)
我一直在玩这个问题,并以相当强大的解决scheme结束:
def get_one_or_create(session, model, create_method='', create_method_kwargs=None, **kwargs): try: return session.query(model).filter_by(**kwargs).one(), False except NoResultFound: kwargs.update(create_method_kwargs or {}) created = getattr(model, create_method, model)(**kwargs) try: session.add(created) session.flush() return created, True except IntegrityError: session.rollback() return session.query(model).filter_by(**kwargs).one(), True
我只是写了一个相当广泛的博客文章的所有细节,但为什么我使用这个几个相当的想法。
-
它解包成一个元组,告诉你该对象是否存在。 这在您的工作stream程中通常很有用。
-
该函数能够使用
@classmethod
装饰的创build者函数(以及特定于它们的属性)。 -
当您有多个进程连接到数据存储时,该解决scheme可以防止竞争条件。
编辑:我已经将session.commit()
更改为session.flush()
如本博文所述 。 请注意,这些决定是特定于所使用的数据存储(Postgres在这种情况下)。
编辑2:我已经更新使用{}作为默认值的函数,因为这是典型的Python陷阱。 感谢您的评论 ,奈杰尔! 如果你对这个问题感到好奇,看看这个StackOverflow问题和这篇博文 。
我想我只是在寻找同样的事情。 这个SQLALchemy配方的工作很好,优雅。
埃里克的优秀答案的修改版本
def get_one_or_create(session, model, create_method='', create_method_kwargs=None, **kwargs): try: return session.query(model).filter_by(**kwargs).one(), True except NoResultFound: kwargs.update(create_method_kwargs or {}) try: with session.begin_nested(): created = getattr(model, create_method, model)(**kwargs) session.add(created) return created, False except IntegrityError: return session.query(model).filter_by(**kwargs).one(), True
- 使用嵌套事务只回滚添加新项目而不是回滚所有内容(请参阅此答案以使用SQLite嵌套事务)
- 移动
create_method
。 如果创build的对象具有关系,并且通过这些关系分配了成员,则会自动添加到会话中。 例如,创build一book
,其中有user_id
和user
作为对应关系,然后在book.user=<user object>
里面的book.user=<user object>
将book
添加到会话中。 这意味着create_method
必须在里面with
从最终的回滚中受益。 请注意,begin_nested
自动触发一次刷新。
请注意,如果使用MySQL,则必须将事务隔离级别设置为READ COMMITTED
而不是REPEATABLE READ
才能工作。 Django的get_or_create (和here )使用相同的策略,另请参阅Django 文档 。
最接近的语义可能是:
def get_or_create(model, **kwargs): """SqlAlchemy implementation of Django's get_or_create. """ session = Session() instance = session.query(model).filter_by(**kwargs).first() if instance: return instance, False else: instance = model(**kwargs) session.add(instance) session.commit() return instance, True
不确定在sqlalchemy中依赖于全局定义的Session
有多洁净,但是Django版本没有连接,所以…
返回的元组包含实例和一个指示实例是否被创build的布尔值(即,如果我们从数据库中读取实例,则返回False)。
Django的get_or_create
经常用于确保全局数据可用,所以我尽可能早地提交。
根据您所采用的隔离级别,以上解决scheme都不可行。 我find的最佳解决scheme是以下forms的RAW SQL:
INSERT INTO table(f1, f2, unique_f3) SELECT 'v1', 'v2', 'v3' WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = 'v3')
无论隔离级别和并行度如何,这都是事务安全的。
注意:为了使其更有效率,对于独特的列有一个INDEX是明智的。
我稍微简化@Kevin。 解决scheme来避免将整个函数包装在if
/ else
语句中。 这样,只有一个return
,我觉得更清洁:
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