【问题标题】:Use temp table with SQLAlchemy使用带有 SQLAlchemy 的临时表
【发布时间】:2023-03-24 20:30:02
【问题描述】:

我正在尝试将临时表与 SQLAlchemy 一起使用,并将其与现有表连接起来。这就是我目前所拥有的

engine = db.get_engine(db.app, 'MY_DATABASE')
df = pd.DataFrame({"id": [1, 2, 3], "value": [100, 200, 300], "date": [date.today(), date.today(), date.today()]})
temp_table = db.Table('#temp_table',
                      db.Column('id', db.Integer),
                      db.Column('value', db.Integer),
                      db.Column('date', db.DateTime))
temp_table.create(engine)
df.to_sql(name='tempdb.dbo.#temp_table',
          con=engine,
          if_exists='append',
          index=False)
query = db.session.query(ExistingTable.id).join(temp_table, temp_table.c.id == ExistingTable.id)
out_df = pd.read_sql(query.statement, engine)
temp_table.drop(engine)
return out_df.to_dict('records')

这不会返回任何结果,因为to_sql 的插入语句没有运行(我认为这是因为它们是使用sp_prepexec 运行的,但我对此并不完全确定)。

然后我尝试只写出 SQL 语句(CREATE TABLE #temp_table...INSERT INTO #temp_table...SELECT [id] FROM...),然后运行pd.read_sql(query, engine)。我收到错误消息

此结果对象不返回行。它已自动关闭。

我猜这是因为该语句不仅仅是SELECT

我该如何解决这个问题(任何一种解决方案都可以,尽管第一种解决方案更可取,因为它避免了硬编码的 SQL)。需要明确的是,我无法修改现有数据库中的架构——它是一个供应商数据库。

【问题讨论】:

  • ExistingTable有记录吗?
  • @AzatIbrakov 是的。实际上,我将其更改为左连接并添加了temp_table.c.date 只是为了确定。我在date 列中用None 返回行。
  • 为什么你的date 列的类型是DateTime 而不是Date
  • @AzatIbrakov 这只是一些测试代码。该表有一个日期时间。不过,我认为这不会对输出产生任何影响,对吗?
  • 我在sqlite 上测试过它会导致问题

标签: python sql-server pandas sqlalchemy temp-tables


【解决方案1】:

如果要在临时表中插入的记录数很少/适中,一种可能性是使用literal subqueryvalues CTE 而不是创建临时表。

# MODEL
class ExistingTable(Base):
    __tablename__ = 'existing_table'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)
    # ...

假设还要将以下数据插入temp 表中:

# This data retrieved from another database and used for filtering
rows = [
    (1, 100, datetime.date(2017, 1, 1)),
    (3, 300, datetime.date(2017, 3, 1)),
    (5, 500, datetime.date(2017, 5, 1)),
]

创建包含该数据的 CTE 或子查询:

stmts = [
    # @NOTE: optimization to reduce the size of the statement:
    # make type cast only for first row, for other rows DB engine will infer
    sa.select([
        sa.cast(sa.literal(i), sa.Integer).label("id"),
        sa.cast(sa.literal(v), sa.Integer).label("value"),
        sa.cast(sa.literal(d), sa.DateTime).label("date"),
    ]) if idx == 0 else
    sa.select([sa.literal(i), sa.literal(v), sa.literal(d)])  # no type cast

    for idx, (i, v, d) in enumerate(rows)
]
subquery = sa.union_all(*stmts)

# Choose one option below.
# I personally prefer B because one could reuse the CTE multiple times in the same query
# subquery = subquery.alias("temp_table")  # option A
subquery = subquery.cte(name="temp_table")  # option B

使用所需的连接和过滤器创建最终查询:

query = (
    session
    .query(ExistingTable.id)
    .join(subquery, subquery.c.id == ExistingTable.id)
    # .filter(subquery.c.date >= XXX_DATE)
)

# TEMP: Test result output
for res in query:
    print(res)    

最后得到pandas数据框:

out_df = pd.read_sql(query.statement, engine)
result = out_df.to_dict('records')

【讨论】:

  • 哈,我只是想在今天早上的通勤路上这样做。我会试一试,然后告诉你。
  • 在性能方面,这个解决方案有多好? Union all 对我来说似乎真的很重......需要对其进行测试,但如果对性能有一些好的想法,那就太棒了......
  • 为什么要使用 UNION ALL?
  • UNION ALL 而不是 UNION 以避免数据库引擎消除重复,因为在这段代码中已经可以做到这一点。请参阅stackoverflow.com/a/49928/99594 了解更多信息。我不认为它是 heavy 因为该解决方案明确地针对不是过多的此类记录。
【解决方案2】:

您可以尝试使用另一种解决方案 - Process-Keyed Table

进程键控表只是一个永久表,用作 临时表。为了允许进程同时使用该表, 表有一个额外的列来标识进程。最简单的方法 这样做是全局变量@@spid(@@spid 是 SQL 中的进程 ID 服务器)。

...

进程键的另一种选择是使用 GUID(数据类型 唯一标识符)。

http://www.sommarskog.se/share_data.html#prockeyed

【讨论】:

  • 您是否建议在 tempdb 中创建此表?我认为无论哪种方式,它都会遇到我在问题的第二部分中遇到的相同问题,即read_sql 不返回任何行。
  • 这个表应该在你的数据库(MY_DATABASE)中创建,而不是临时数据库。这不好,但应该可以。
  • 我无权在该数据库中创建表。这是一个供应商数据库。
猜你喜欢
  • 2021-05-18
  • 2013-12-03
  • 2011-06-11
  • 2020-09-10
  • 2012-03-24
  • 2015-07-27
  • 1970-01-01
  • 2015-10-06
  • 1970-01-01
相关资源
最近更新 更多