您不需要在本地加载这些对象,您真正想做的就是拥有database创建这些行。
您本质上想要运行一个从现有行创建行的查询:
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
每个新行的语句。
像这样的查询是你可以做的事情SQL炼金术core https://docs.sqlalchemy.org/en/stable/core/index.html,处理生成 SQL 语句的 API 的一半。然而,你can使用从 a 构建的查询声明式 ORM 模型 https://docs.sqlalchemy.org/en/stable/orm/extensions/declarative/index.html作为起点。你需要
- 访问Table实例 https://docs.sqlalchemy.org/en/13/core/metadata.html#sqlalchemy.schema.Table对于模型,因为这样您就可以创建一个
INSERT
声明通过Table.insert() method https://docs.sqlalchemy.org/en/stable/core/metadata.html#sqlalchemy.schema.Table.insert.
您还可以从以下位置获取相同的对象models.Product
查询,稍后详细介绍。
- 访问通常会为您的 Python 实例获取数据的语句以进行过滤
models.Product
询问;您可以通过Query.statement财产 https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.statement.
- 更新语句以替换包含的
activity_id
列与您的新值,并删除主键(我假设您有一个自动递增的主键列)。
- 将更新后的语句应用到
Insert
表的对象通过Insert.from_select() https://docs.sqlalchemy.org/en/stable/core/dml.html#sqlalchemy.sql.expression.Insert.from_select.
- 执行生成的
INSERT INTO ... FROM ...
query.
步骤 1 可以通过使用SQLAlchemy 自省 API https://docs.sqlalchemy.org/en/stable/core/inspection.html; the inspect()功能 https://docs.sqlalchemy.org/en/stable/core/inspection.html#sqlalchemy.inspect,应用于模型类,给你一个Mapper实例 https://docs.sqlalchemy.org/en/stable/orm/mapping_api.html#sqlalchemy.orm.Mapper,这又具有Mapper.local_table属性 https://docs.sqlalchemy.org/en/stable/orm/mapping_api.html#sqlalchemy.orm.Mapper.local_table.
步骤 2 和 3 需要稍微调整一下Select.with_only_columns() method https://docs.sqlalchemy.org/en/stable/core/selectable.html#sqlalchemy.sql.expression.Select.with_only_columns产生一个新的SELECT
我们交换列的语句。你不能轻易removeselect 语句中的列,但是我们可以使用循环查询中现有的列 https://docs.sqlalchemy.org/en/stable/core/selectable.html#sqlalchemy.sql.expression.Select.columns将它们“复制”到新的SELECT
,同时进行我们的更换。
第 4 步就很简单了,Insert.from_select()
需要插入的列和SELECT
询问。我们都有SELECT
我们拥有的对象也给了我们它的列。
这是生成您的代码INSERT
; the **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)