我有一张牌桌叫 products
具有以下列
id
、product_id
、data
、activity_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()
执行这些批量输入的最佳/最快方法是什么,因此它可以消磨时间,到目前为止性能还可以,是否有更好的解决方案?
答案 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 构建的查询作为起点。你需要
Table
instance,因为这样您就可以通过 Table.insert()
method 创建一个 INSERT
语句。models.Product
查询中获取相同的对象,稍后会详细介绍。models.Product
查询获取 Python 实例数据的语句;您可以通过 Query.statement
property 执行此操作。activity_id
列,并删除主键(我假设您有一个自动递增的主键列)。Insert.from_select()
将更新后的语句应用于表的 Insert
对象。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)