SQLAlchemy临时表痛苦的解决方案?

时间:2016-01-20 01:24:51

标签: python postgresql sqlalchemy temp-tables

似乎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))

除非我做一些有创意的事情,否则我必须记住明确删除它,然后使用ON COMMIT DROP进行渲染:

MyTempTable.__table__.create(session.bind)

另外,我刚刚给出的内容甚至不起作用,除非临时表是“顶级”。我还没有完全弄明白(因为不想花时间调查它为什么不起作用),但基本上我尝试使用session.begin_nested()和你在嵌套事务中以这种方式创建一个临时表最终得到一个错误,说该关系不存在。但是,我有几种情况我在嵌套事务中创建临时表以进行单元测试,它们工作得很好。检查回声输出,看来差别在于一个在BEGIN语句之前呈现,而另一个在它之后呈现。这是使用Postgresql。

在嵌套事务中工作的内容,坦率地说,节省了大量时间,就是输入该死的sql并使用session.execute执行它。

MyTempTable.__table__.drop(session.bind)

当然,如果你这样做,你仍然需要一个相应的表模型来使用ORM功能,或者必须坚持使用原始的SQL查询,这首先会破坏SQLAlchemy的目的。

我想知道我是否在这里遗漏了一些东西,或者有人提出了一个更优雅的解决方案。

2 个答案:

答案 0 :(得分:0)

我将ORM与Core一起使用。 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)

答案 1 :(得分:0)

我决定以 this 答案为基础,因为我想要一种更灵活的方式来从现有模型创建副本表,同时仍然支持索引定义并与 alembic* 配合使用。

我发现这种方法对于创建真正的临时表和创建将与主表交换的动态表都很有用。如果定义不完全匹配,您可能会遇到更棘手的 alembic 场景。

*根据我的特定使用模式

import time

from sqlalchemy.schema import CreateTable


def copy_table_args(model, **kwargs):
    """Try to copy existing __table_args__, override params with 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(arg)
            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)
            else:
                # TODO: need to handle 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)

    args = {c.name: c.copy() for c in model.__table__.c}
    args["__tablename__"] = table_name
    args["__table_args__"] = table_args

    copy_model = type(model_name, model.__bases__, args)
    print(str(CreateTable(copy_model.__table__)))
    copy_model.__table__.create(conn)
    return copy_model


def temp_table_from_model(conn, model, **kwargs):
    return copy_table_from_model(conn, model, prefixes=["TEMPORARY"])

注意:我没有添加处理复制约束的逻辑,这是针对 MySQL 的轻微测试。