J'essaie d'utiliser une table temporaire avec SQLAlchemy et de la joindre à une table existante. C'est ce que j'ai jusqu'ici
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')
Cela ne renvoie aucun résultat car les instructions insérées que to_sql
ne fait pas exécuter (je pense que c'est parce qu'elles sont exécutées à l'aide de sp_prepexec
, mais je n'en suis pas tout à fait sûr).
J'ai ensuite essayé d'écrire simplement l'instruction SQL (CREATE TABLE #temp_table...
, INSERT INTO #temp_table...
, SELECT [id] FROM...
) puis d'exécuter pd.read_sql(query, engine)
. Je reçois le message d'erreur
Cet objet de résultat ne renvoie pas de lignes. Il a été fermé automatiquement.
J'imagine que c'est parce que l'énoncé fait plus que SELECT
?
Comment puis-je résoudre ce problème (l'une ou l'autre solution fonctionnerait, bien que la première soit préférable car elle évite le code SQL codé en dur). Pour être clair, je ne peux pas modifier le schéma dans la base de données existante - c'est une base de données fournisseur.
Si le nombre d'enregistrements à insérer dans la table temporaire est faible ou modéré, une possibilité serait d'utiliser un literal subquery
ou un values CTE
au lieu de créer une table temporaire.
# MODEL
class ExistingTable(Base):
__table= 'existing_table'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
# ...
Supposons également que les données suivantes doivent être insérées dans la table 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)),
]
Créez un CTE ou une sous-requête contenant ces données:
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
Créez une requête finale avec les jointures et les filtres requis:
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)
Enfin, obtenez le cadre de données sur les pandas:
out_df = pd.read_sql(query.statement, engine)
result = out_df.to_dict('records')
Vous pouvez essayer d'utiliser une autre solution - Process-Keyed Table
Une table de processus est simplement une table permanente qui sert de table temporaire . Pour permettre aux processus d'utiliser la table simultanément, la table Comporte une colonne supplémentaire pour identifier le processus. La méthode la plus simple pour Est la variable globale @@ spid (@@ spid est l'ID de processus dans SQL Server).
...
Une alternative pour la clé de processus consiste à utiliser un GUID (type de données Uniqueidentifier).