执行批量插入 SQLAlchemy

时间:2021-02-26 18:29:29

标签: python sqlalchemy flask-sqlalchemy

我有一张牌桌叫 products

具有以下列 idproduct_iddataactivity_id

我实际上想要做的是复制大量现有产品并更新它的 activity_id 并在产品表中创建新条目。 示例:

我已经有 70 个现有的产品条目,其 activity_id 为 2

现在我想用相同的数据创建另外 70 个条目,除了更新的 activity_id

我可能有数千个要复制的现有条目,并将复制的条目 activity_id 更新为新 ID。

products = self.session.query(model.Products).filter(filter1, filter2).all()

这将返回过滤器的所有现有产品。

然后我遍历产品,然后简单地克隆现有产品并更新 activity_id 字段。

 for product in products:
                product.activity_id = new_id

 self.uow.skus.bulk_save_objects(simulation_skus)
 self.uow.flush()
 self.uow.commit()

执行这些批量输入的最佳/最快方法是什么,因此它可以消磨时间,到目前为止性能还可以,是否有更好的解决方案?

1 个答案:

答案 0 :(得分:3)

您不需要在本地加载这些对象,您真正想做的就是让数据库创建这些行。

您本质上是想运行一个从现有行创建行的查询:

INSERT INTO product (product_id, data, activity_id)
SELECT product_id, data, 2  -- the new activity_id value
FROM product
WHERE activity_id = old_id

上述查询将完全在数据库服务器上运行;这比将查询加载到 Python 对象中,然后将所有 Python 数据发送回服务器以填充每个新行的 INSERT 语句要好得多。

您可以使用 SQLAlchemy core 执行此类查询,这是处理生成 SQL 语句的 API 的一半。但是,您可以使用从 declarative ORM model 构建的查询作为起点。你需要

  1. 访问模型的 Table instance,因为这样您就可以通过 Table.insert() method 创建一个 INSERT 语句。
    您还可以从 models.Product 查询中获取相同的对象,稍后会详细介绍。
  2. 访问通常会为过滤的 models.Product 查询获取 Python 实例数据的语句;您可以通过 Query.statement property 执行此操作。
  3. 更新语句,用您的新值替换包含的 activity_id 列,并删除主键(我假设您有一个自动递增的主键列)。
  4. 通过 Insert.from_select() 将更新后的语句应用于表的 Insert 对象。
  5. 执行生成的 INSERT INTO ... FROM ... 查询。

步骤 1 可以通过使用 SQLAlchemy introspection API 来实现;应用于模型类的 inspect() function 为您提供一个 Mapper instance,而后者又具有一个 Mapper.local_table attribute

第 2 步和第 3 步需要对 Select.with_only_columns() method 进行一些处理,以生成一个新的 SELECT 语句,我们在其中交换了列。您不能轻松地从 select 语句中删除列,但是我们可以使用 existing columns in the query 上的循环将它们“复制”到新的 SELECT,同时进行我们的更换。

第 4 步很简单,Insert.from_select() 需要插入的列和 SELECT 查询。我们有两个,因为我们拥有的 SELECT 对象也为我们提供了它的列。

这是生成INSERT的代码; **replace 关键字参数是插入时要替换的列:

from sqlalchemy import inspect, literal
from sqlalchemy.sql import ClauseElement

def insert_from_query(model, query, **replace):
    # The SQLAlchemy core definition of the table
    table = inspect(model).local_table
    # and the underlying core select statement to source new rows from
    select = query.statement

    # validate asssumptions: make sure the query produces rows from the above table
    assert table in select.froms, f"{query!r} must produce rows from {model!r}"
    assert all(c.name in select.columns for c in table.columns), f"{query!r} must include all {model!r} columns"

    # updated select, replacing the indicated columns
    as_clause = lambda v: literal(v) if not isinstance(v, ClauseElement) else v
    replacements = {name: as_clause(value).label(name) for name, value in replace.items()}
    from_select = select.with_only_columns([
        replacements.get(c.name, c)
        for c in table.columns
        if not c.primary_key
    ])
        
    return table.insert().from_select(from_select.columns, from_select)

我包含了一些关于模型和查询关系的断言,并且代码接受任意列子句作为替换,而不仅仅是文字值。例如,您可以使用 func.max(models.Product.activity_id) + 1 作为替换值(包装为子选择)。

上述函数执行步骤 1-4,在打印时生成所需的 INSERT SQL 语句(我创建了一个我认为可能具有代表性的 products 模型和查询):

>>> print(insert_from_query(models.Product, products, activity_id=2))
INSERT INTO products (product_id, data, activity_id) SELECT products.product_id, products.data, :param_1 AS activity_id
FROM products
WHERE products.activity_id != :activity_id_1

你所要做的就是执行它:

insert_stmt = insert_from_query(models.Product, products, activity_id=2)
self.session.execute(insert_stmt)