140

我正在启动一个新应用程序并考虑使用 ORM——特别是 SQLAlchemy。

假设我的数据库中有一个“foo”列,我想增加它。在直接的 sqlite 中,这很容易:

db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')

我想出了等效的 SQLAlchemy SQL-builder:

engine = sqlalchemy.create_engine('sqlite:///mydata.sqlitedb')
md = sqlalchemy.MetaData(engine)
table = sqlalchemy.Table('stuff', md, autoload=True)
upd = table.update(values={table.c.foo:table.c.foo+1})
engine.execute(upd)

这有点慢,但没有太多。

这是我对 SQLAlchemy ORM 方法的最佳猜测:

# snip definition of Stuff class made using declarative_base
# snip creation of session object
for c in session.query(Stuff):
    c.foo = c.foo + 1
session.flush()
session.commit()

这样做是正确的,但它需要的时间是其他两种方法的不到 50 倍。我认为那是因为它必须先将所有数据带入内存,然后才能使用它。

有没有办法使用 SQLAlchemy 的 ORM 生成高效的 SQL?或者使用任何其他python ORM?还是我应该回去手动编写 SQL?

4

6 回答 6

206

SQLAlchemy 的 ORM 旨在与 SQL 层一起使用,而不是隐藏它。但是在同一事务中使用 ORM 和普通 SQL 时,您必须牢记一两件事。基本上,从一方面来看,ORM 数据修改只会在您从会话中刷新更改时才会影响数据库。另一方面,SQL 数据操作语句不会影响会话中的对象。

所以如果你说

for c in session.query(Stuff).all():
    c.foo = c.foo+1
session.commit()

它会按照它说的去做,从数据库中获取所有对象,修改所有对象,然后在将更改刷新到数据库时,一一更新行。

相反,您应该这样做:

session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1}))
session.commit()

这将像您期望的那样作为一个查询执行,并且因为至少默认会话配置会在提交时使会话中的所有数据过期,所以您没有任何陈旧数据问题。

在即将发布的 0.5 系列中,您还可以使用此方法进行更新:

session.query(Stuff).update({Stuff.foo: Stuff.foo + 1})
session.commit()

这将基本上运行与前一个片段相同的 SQL 语句,但还会选择更改的行并使会话中的任何陈旧数据失效。如果您知道更新后没有使用任何会话数据,您还可以添加synchronize_session=False到更新语句并摆脱该选择。

于 2008-11-10T17:40:42.797 回答
112
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()

试试这个 =)

于 2010-12-27T16:28:03.490 回答
40

有几种使用 sqlalchemy 进行更新的方法

1) for c in session.query(Stuff).all():
       c.foo += 1
   session.commit()

2) session.query(Stuff).update({"foo": Stuff.foo + 1})
   session.commit()

3) conn = engine.connect()
   table = Stuff.__table__
   stmt = table.update().values({'foo': Stuff.foo + 'a'})
   conn.execute(stmt)
   conn.commit()
于 2015-11-10T19:53:27.213 回答
10

以下是如何解决相同问题而无需手动映射字段的示例:

from sqlalchemy import Column, ForeignKey, Integer, String, Date, DateTime, text, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.attributes import InstrumentedAttribute

engine = create_engine('postgres://postgres@localhost:5432/database')
session = sessionmaker()
session.configure(bind=engine)

Base = declarative_base()


class Media(Base):
  __tablename__ = 'media'
  id = Column(Integer, primary_key=True)
  title = Column(String, nullable=False)
  slug = Column(String, nullable=False)
  type = Column(String, nullable=False)

  def update(self):
    s = session()
    mapped_values = {}
    for item in Media.__dict__.iteritems():
      field_name = item[0]
      field_type = item[1]
      is_column = isinstance(field_type, InstrumentedAttribute)
      if is_column:
        mapped_values[field_name] = getattr(self, field_name)

    s.query(Media).filter(Media.id == self.id).update(mapped_values)
    s.commit()

因此,要更新 Media 实例,您可以执行以下操作:

media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()
于 2015-09-08T00:10:34.817 回答
2

如果是因为创建对象的开销,那么 SA 可能根本无法加速。

如果是因为它正在加载相关对象,那么您可以通过延迟加载来做一些事情。由于引用而创建了很多对象吗?(IE,获取 Company 对象也会获取所有相关的 People 对象)。

于 2008-11-07T01:08:04.187 回答
1

没有经过测试,我会尝试:

for c in session.query(Stuff).all():
     c.foo = c.foo+1
session.commit()

(IIRC,commit() 在没有 flush() 的情况下工作)。

我发现有时进行大型查询然后在 python 中进行迭代可能比大量查询快 2 个数量级。我假设遍历查询对象的效率低于遍历查询对象的 all() 方法生成的列表。

[请注意下面的评论-这根本没有加快速度]。

于 2008-11-07T00:35:19.697 回答