使用SQLAlchemy ORM有效地更新数据库

Joh*_*uhy 107 python orm sqlalchemy

我正在开始一个新的应用程序,并考虑使用ORM - 特别是SQLAlchemy.

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

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

我想出了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)
Run Code Online (Sandbox Code Playgroud)

这稍微慢一点,但其中并不多.

这是我对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()
Run Code Online (Sandbox Code Playgroud)

这是正确的,但它只需要不到其他两个方法的五十倍.我认为这是因为它必须将所有数据带入内存才能使用它.

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

Ant*_*sma 168

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

所以,如果你说

for c in session.query(Stuff).all():
    c.foo = c.foo+1
session.commit()
Run Code Online (Sandbox Code Playgroud)

它将执行它所说的内容,从数据库中获取所有对象,修改所有对象,然后在将更改刷新到数据库时,逐个更新行.

相反,你应该这样做:

session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1}))
session.commit()
Run Code Online (Sandbox Code Playgroud)

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

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

session.query(Stuff).update({Stuff.foo: Stuff.foo + 1})
session.commit()
Run Code Online (Sandbox Code Playgroud)

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

  • 在第三种方式中,它会触发 orm 事件(如 after_update)吗? (2认同)

小智 83

session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()
Run Code Online (Sandbox Code Playgroud)

试试这个=)

  • 对于那些在使用此方法时仍然存在性能问题的人:默认情况下,这可能会首先为每个记录执行SELECT,之后仅更新UPDATE.将synchronize_session = False传递给update()方法会阻止这种情况发生,但如果在commit()之前不再使用您再次更新的对象,请确保只执行此操作. (5认同)

Nim*_*ush 22

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

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

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

3) conn = engine.connect()
   stmt = Stuff.update().\
       values(Stuff.foo = (Stuff.foo + 1))
   conn.execute(stmt)
Run Code Online (Sandbox Code Playgroud)


plo*_*man 8

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

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()
Run Code Online (Sandbox Code Playgroud)

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

media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()
Run Code Online (Sandbox Code Playgroud)