Come eseguire SQL raw nell'app Flask-SQLAlchemy


219

Come si esegue SQL grezzo in SQLAlchemy?

Ho un'app Web Python che funziona su pallone e si interfaccia al database tramite SQLAlchemy.

Ho bisogno di un modo per eseguire l'SQL grezzo. La query coinvolge più join di tabella insieme a viste Inline.

Ho provato:

connection = db.session.connection()
connection.execute( <sql here> )

Ma continuo a ricevere errori del gateway.


5
L'ho già visto prima, ma non sono riuscito a trovare un tutorial su come eseguire un aggiornamento. Preferirei anche non imparare la sintassi e nascondere una query SQL piuttosto lunga (circa 20 righe).
Starwing123

103
@MarkusUnterwaditzer Lo pensavo, ma ora non sono molto d'accordo. L'SQL non elaborato e correttamente parametrizzato è generalmente molto più facile da leggere e gestire rispetto a un gruppo di chiamate di funzioni e oggetti che lo generano. Ti offre anche tutte le funzionalità del database senza dover saltare i cerchi per fare in modo che l'ORM generi la sintassi corretta (se possibile) e impedisce all'ORM di fare cose inaspettate. Potresti porre la domanda "Allora perché usare SQLAlchemy?" E l'unica risposta che ho è: "L'applicazione esistente la usa e cambiare tutto è troppo costoso".
jpmc26,

4
@ jpmc26 Aumentato il tuo commento, in quanto amante di SQL, ho difficoltà a pensare di "dare le chiavi del database" a un alchimista irresponsabile e tendere a appoggiarmi sul lato di ORM è un antipasto :) ha detto che sarei desideroso di accelerare alcuni componenti, come la registrazione / gestione degli utenti, e anche la generazione di tabelle con sequenze di pulsanti per le quali posso codificare le azioni + SQL. Ti sei imbattuto in strumenti compatibili con gli scettici ORM che funzionano bene per te in un framework Python?
zx81

@ jpmc26 Cosa usi in un framework Python per usare solo SQL o abbastanza vicino come Dapper C #? Tutto ciò che vedo in un framework Web Python mi vuole usare SQLAlchemy, e non mi piace un ORM, e se ne uso uno, è estremamente minimale.
johnny,

@johnny Non ho avuto l'opportunità di provarlo da solo, ma probabilmente le librerie di connessione al database non elaborate sono sufficienti. Ad esempio, psycopg2 ha i cursori che restituiscono namedtuplee dictdirettamente: initd.org/psycopg/docs/extras.html .
jpmc26,

Risposte:


310

Hai provato:

result = db.engine.execute("<sql here>")

o:

from sqlalchemy import text

sql = text('select name from penguins')
result = db.engine.execute(sql)
names = [row[0] for row in result]
print names

7
Se si effettua un inserimento o un aggiornamento, come si effettua la transazione?
David S,

14
Se si utilizza SQL non elaborato, si controllano le transazioni, quindi è necessario emettere le dichiarazioni BEGINe COMMIT.
Miguel,

1
Gli stessi comandi SQL funzionano quando vengono emessi senza SQLAlchemy? Potresti voler abilitare il debug sul tuo database in modo da poter vedere quali comandi sta eseguendo.
Miguel,

27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))esegue e lo commette anche.
Devi

8
@Miguel "Se si utilizza SQL non elaborato, si controllano le transazioni, quindi è necessario emettere le istruzioni BEGIN e COMMIT." Questo semplicemente non è vero. È possibile utilizzare SQL raw con un oggetto sessione. Ho appena notato questo commento, ma puoi vedere la mia risposta su come utilizzare una sessione con SQL non elaborato.
jpmc26,

180

Gli oggetti sessione Alchemy SQL hanno il loro executemetodo:

result = db.session.execute('SELECT * FROM my_table WHERE my_column = :val', {'val': 5})

Tutte le query dell'applicazione dovrebbero passare attraverso un oggetto sessione, siano esse SQL grezzo o meno. Ciò garantisce che le query siano gestite correttamente da una transazione , il che consente di eseguire il commit o il rollback di più query nella stessa richiesta come una singola unità. Uscire dalla transazione utilizzando il motore o la connessione ti espone a un rischio molto maggiore di bug sottili, forse difficili da rilevare, che possono lasciarti con dati danneggiati. Ogni richiesta deve essere associata a una sola transazione e utilizzodb.session garantirà che ciò per l'applicazione.

Si noti inoltre che executeè progettato per query con parametri . Usa parametri, come:val nell'esempio, per qualsiasi input alla query per proteggersi dagli attacchi SQL injection. È possibile fornire il valore per questi parametri passando adict come secondo argomento, dove ogni chiave è il nome del parametro come appare nella query. L'esatta sintassi del parametro stesso può variare a seconda del database, ma tutti i principali database relazionali li supportano in qualche modo.

Supponendo che sia una SELECTquery, questo restituirà un iterabile diRowProxy oggetti.

Puoi accedere alle singole colonne con una varietà di tecniche:

for r in result:
    print(r[0]) # Access by positional index
    print(r['my_column']) # Access by column name as a string
    r_dict = dict(r.items()) # convert to dict keyed by column names

Personalmente, preferisco convertire i risultati in namedtuples:

from collections import namedtuple

Record = namedtuple('Record', result.keys())
records = [Record(*r) for r in result.fetchall()]
for r in records:
    print(r.my_column)
    print(r)

Se non si utilizza l'estensione Flask-SQLAlchemy, è comunque possibile utilizzare facilmente una sessione:

import sqlalchemy
from sqlalchemy.orm import sessionmaker, scoped_session

engine = sqlalchemy.create_engine('my connection string')
Session = scoped_session(sessionmaker(bind=engine))

s = Session()
result = s.execute('SELECT * FROM my_table WHERE my_column = :val', {'val': 5})

Una selezione restituirà un ResultProxy.
Alan B,

@AlanB Sì. Ho scelto male le mie parole quando l'ho chiamata sequenza, sottintendendo che implementa il protocollo di sequenza. Ho corretto e chiarito. Grazie.
jpmc26,

@ jpmc26 dovrebbe chiudere la sessione dopo aver eseguito la query come db.session.close ()? E avrà ancora i vantaggi del pool di connessioni?
Ravi Malhotra,

58

docs: Esercitazione sul linguaggio delle espressioni SQL - Utilizzo del testo

esempio:

from sqlalchemy.sql import text

connection = engine.connect()

# recommended
cmd = 'select * from Employees where EmployeeGroup = :group'
employeeGroup = 'Staff'
employees = connection.execute(text(cmd), group = employeeGroup)

# or - wee more difficult to interpret the command
employeeGroup = 'Staff'
employees = connection.execute(
                  text('select * from Employees where EmployeeGroup = :group'), 
                  group = employeeGroup)

# or - notice the requirement to quote 'Staff'
employees = connection.execute(
                  text("select * from Employees where EmployeeGroup = 'Staff'"))


for employee in employees: logger.debug(employee)
# output
(0, 'Tim', 'Gurra', 'Staff', '991-509-9284')
(1, 'Jim', 'Carey', 'Staff', '832-252-1910')
(2, 'Lee', 'Asher', 'Staff', '897-747-1564')
(3, 'Ben', 'Hayes', 'Staff', '584-255-2631')

1
Il link ai documenti sqlalchemy sembra essere obsoleto. Questo è più recente: docs.sqlalchemy.org/en/latest/core/…
Carl

1
Posso chiederti perché stiamo usando ==?
Nam G VU,

1
@Jake Berger un grande grazie per te. Ho perso quasi un giorno alla ricerca di questa risposta. Stavo solo eseguendo direttamente il sql senza convertirlo in testo. Stava generando errori ogni volta che abbiamo% studenti% nella mia clausola where. Un grande applauso per la tua risposta.
Suresh Kumar,

1
@NamGVU perché come nella maggior parte dei linguaggi di programmazione, =è normalmente riservato per assegnare un valore; mentre ==è riservato per il confronto dei valori
Jake Berger,

2
@JakeBerger Hai un link per quello? SQL non è un linguaggio del genere e, a giudicare dai documenti SQLAlchemy, non è così.
johndodo,

36

È possibile ottenere i risultati delle query SELECT SQL utilizzando from_statement()e text()come mostrato qui . Non devi affrontare le tuple in questo modo. Ad esempio per una classe Usercon il nome della tabella usersche puoi provare,

from sqlalchemy.sql import text
.
.
.
user = session.query(User).from_statement(
    text("SELECT * FROM users where name=:name")).\
    params(name='ed').all()

return user

15
result = db.engine.execute(text("<sql here>"))

esegue il <sql here>ma non lo commette a meno che tu non sia attivoautocommit modalità. Pertanto, inserimenti e aggiornamenti non si rifletterebbero nel database.

Per eseguire il commit dopo le modifiche, fare

result = db.engine.execute(text("<sql here>").execution_options(autocommit=True))

2

Questa è una risposta semplificata su come eseguire query SQL da Flask Shell

Innanzitutto, mappare il modulo (se il modulo / app è manage.py nella cartella principale e ci si trova in un sistema operativo UNIX), eseguire:

export FLASK_APP=manage

Esegui la shell Flask

flask shell

Importa ciò di cui abbiamo bisogno ::

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
from sqlalchemy import text

Esegui la tua query:

result = db.engine.execute(text("<sql here>").execution_options(autocommit=True))

Questo utilizza la connessione al database attualmente che ha l'applicazione.


0

Hai provato a utilizzare connection.execute(text( <sql here> ), <bind params here> )e associare i parametri come descritto nei documenti ? Questo può aiutare a risolvere molti problemi di formattazione e prestazioni dei parametri. Forse l'errore del gateway è un timeout? I parametri di bind tendono a rendere le query complesse eseguite in modo sostanzialmente più veloce.


2
secondo i documenti , dovrebbe essere connection.execute(text(<sql here>), <bind params> ). bind paramsNON dovrebbe esserci text(). inserendo i parametri di bind nel metodo execute ()
Jake Berger,

Il collegamento di Jake è interrotto. Penso che questo sia l'URL che è rilevante ora: docs.sqlalchemy.org/en/latest/core/…
code_dredd

-1

Se si vuole evitare di tuple, un altro modo è chiamando i first, oneo allmetodi:

query = db.engine.execute("SELECT * FROM blogs "
                           "WHERE id = 1 ")

assert query.first().name == "Welcome to my blog"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.