[英]A solution to SQLAlchemy temporary table pain?
SQLAlchemy 的最大缺點似乎是它在處理臨時表時需要倒退幾步。 例如,一個非常常見的用例是創建一個非常特定於一項任務的臨時表,將一些數據放入其中,然后對其進行連接。
對於初學者來說,聲明臨時表是冗長且有限的。 請注意,在這個例子中我不得不編輯它,因為我的類實際上繼承了一個基類,所以我在這里給出的可能有點不正確。
@as_declarative(metaclass=MetaBase)
class MyTempTable(object):
__tablename__ = "temp"
__table_args__ = {'prefixes': ['TEMPORARY']}
id = Column(Integer(), primary_key=True)
person_id = Column(BigInteger())
a_string = Column(String(100))
創建它是不直觀的:
MyTempTable.__table__.create(session.bind)
我還必須記住明確刪除它,除非我做一些有創意的事情讓它在 ON COMMIT DROP 下渲染:
MyTempTable.__table__.drop(session.bind)
另外,除非臨時表是“頂級”,否則我剛才給出的內容甚至都不起作用。 我仍然沒有完全弄清楚這一點(因為不想花時間調查為什么它不起作用),但基本上我嘗試使用 session.begin_nested() 在嵌套事務中以這種方式創建一個臨時表,你最終出現錯誤,指出關系不存在。 但是,我有幾個案例,出於單元測試的目的,我在嵌套事務中創建了一個臨時表,它們工作得很好。 檢查 echo 輸出,不同之處在於一個在 BEGIN 語句之前呈現,而另一個在它之后呈現。 這是使用 Postgresql。
在嵌套事務中起作用並坦率地為您節省大量時間的是,只需鍵入該死的 sql 並使用 session.execute 執行它。
session.execute(text(
"CREATE TEMPORARY TABLE temp ("
" id SERIAL,"
" person_id BIGINT,"
" a_string TEXT"
") ON COMMIT DROP;"
))
當然,如果你這樣做,你仍然需要一個相應的表模型來使用 ORM 功能,或者必須堅持使用原始 sql 查詢,這首先違背了 SQLAlchemy 的目的。
我想知道我是否在這里遺漏了什么,或者是否有人想出了一個更優雅的解決方案。
我將 ORM 與核心一起使用。 ORM 保留用於更高級別的操作。 對於大量數據和臨時表,Core 更方便。 例子:
temptbl_name = 'temp_del_dup_pk_{}'.format(datestamp)
temptbl = Table(temptbl_name, metadata, Column('col1', Integer, index=True),..., extend_existing=True)
temptbl.create(engine)
更新這是一個簡單的函數,可以動態生成臨時表 ORM 定義:
def temp_table(name, cols):
args = dict(col1=Column(Integer, index=True),...)
args['__tablename__'] = name
args['__table_args__'] = dict(extend_existing=True)
return type(name, (Base,), args)
鏡像現有表的列可能很有用:
def temp_table(name, base_table):
args = {c.name:c.copy() for c in base_table.__table__.c}
...
我決定以這個答案為基礎,因為我想要一種更靈活的方法來從現有模型創建復制表,同時仍然支持索引定義並與alembic
* 一起玩。
我發現這種方法對於創建真正的臨時表和創建將與主表交換的動態表都很有用。 如果定義不完全匹配,后者可能會遇到更棘手的alembic
場景。
*以我特定的使用模式
import time
import warnings
import sqlalchemy as sa
def copy_table_args(model, **kwargs):
table_args = model.__table_args__
if isinstance(table_args, tuple):
new_args = []
for arg in table_args:
if isinstance(arg, dict):
table_args_dict = arg.copy()
table_args_dict.update(**kwargs)
new_args.append(table_args_dict)
elif isinstance(arg, sa.Index):
index = sa.Index(
arg.name,
*[col for col in arg.columns.keys()],
unique=arg.unique,
**arg.kwargs,
)
new_args.append(index)
elif isinstance(arg, sa.UniqueConstraint):
new_args.append(arg.copy())
else:
# TODO: need to handle other Constraints
raise Exception(f"Unhandled table arg: {arg}")
table_args = tuple(new_args)
elif isinstance(table_args, dict):
table_args = {
k: (v.copy() if hasattr(v, "copy") else v) for k, v in table_args.items()
}
table_args.update(**kwargs)
else:
raise Exception(f"Unexpected __table_args__ type: {table_args}")
return table_args
def copy_table_from_model(conn, model, **kwargs):
model_name = model.__name__ + "Tmp"
table_name = model.__table__.name + "_" + str(time.time()).replace(".", "_")
table_args = copy_table_args(model, extend_existing=True, **kwargs)
args = {c.name: c.copy() for c in model.__table__.c}
args["__tablename__"] = table_name
args["__table_args__"] = table_args
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=sa.exc.SAWarning)
copy_model = type(model_name, model.__bases__, args)
copy_model.__table__.create(conn)
return copy_model
def temp_table_from_model(conn, model):
return copy_table_from_model(conn, model, prefixes=["TEMPORARY"])
注意:我沒有添加邏輯來處理復制約束,這是針對 MySQL 進行的輕微測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.