Cómo ejecutar SQL sin procesar en la aplicación Flask-SQLAlchemy

219

¿Cómo se ejecuta SQL sin procesar en SQLAlchemy?

Tengo una aplicación web de Python que se ejecuta en un matraz e interfaces con la base de datos a través de SQLAlchemy.

Necesito una forma de ejecutar el SQL sin formato. La consulta involucra múltiples combinaciones de tablas junto con vistas en línea.

He intentado:

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

Pero sigo recibiendo errores de puerta de enlace.

starwing123
fuente
55
Lo he visto antes, pero no pude encontrar un tutorial sobre cómo ejecutar una actualización. También prefiero no aprender la sintaxis y encubrir una consulta SQL bastante larga (aproximadamente 20 líneas).
starwing123
103
@ MarkusUnterwaditzer Solía ​​pensar eso, pero ahora estoy totalmente en desacuerdo. El SQL en bruto, correctamente parametrizado es generalmente mucho más fácil de leer y mantener que un montón de llamadas a funciones y objetos que lo generan. También le brinda todas las capacidades de la base de datos sin tener que saltar a través de aros para hacer que el ORM genere la sintaxis correcta (si es posible) y evita que el ORM haga cosas inesperadas. Puede hacer la pregunta, "¿Entonces por qué usar SQLAlchemy?", Y la única respuesta que tengo es: "La aplicación existente lo usa y cambiar todo es demasiado costoso".
jpmc26
44
@ jpmc26 Subió su comentario: como amante de SQL, me resulta difícil la idea de "regalar las claves de la base de datos" a un alquimista irresponsable y tender a apoyarse en ORM es un antipatrtern :) Que ser Dijo que me gustaría acelerar ciertos componentes, como el registro / gestión de usuarios, y también la generación de tablas con secuencias de botones para las cuales puedo codificar las acciones + SQL. ¿Te has encontrado con algunas herramientas amigables con los escépticos de ORM que te funcionen bien en un marco de Python?
zx81
@ jpmc26 ¿Qué usa en un marco de Python para usar solo SQL o bastante cerca como C # Dapper? Todo lo que veo en un marco web de Python quiere que use SQLAlchemy, y no me gusta un ORM, y si uso uno, es extremadamente mínimo.
Johnny
@johnny No he tenido la oportunidad de probarlo yo mismo, pero las bibliotecas de conexión de base de datos sin procesar son probablemente suficientes. Por ejemplo, psycopg2 tiene cursores que regresan namedtupley dictdirectamente: initd.org/psycopg/docs/extras.html .
jpmc26

Respuestas:

310

Has probado:

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
Miguel
fuente
77
Si realiza una inserción o actualización, ¿cómo confirma la transacción?
David S
14
Si está utilizando SQL sin procesar, entonces controla las transacciones, por lo que debe emitir las declaraciones BEGINy COMMITusted mismo.
Miguel
1
¿Funcionan los mismos comandos SQL cuando los emite sin SQLAlchemy? Es posible que desee habilitar la depuración en su base de datos para que pueda ver qué comandos está ejecutando.
Miguel
27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))ejecuta y lo compromete también.
Devi
8
@Miguel "Si está utilizando SQL sin procesar, entonces controla las transacciones, por lo que debe emitir las declaraciones BEGIN y COMMIT usted mismo". Esto simplemente no es cierto. Puede usar SQL sin formato con un objeto de sesión. Acabo de notar este comentario, pero puedes ver mi respuesta sobre cómo usar una sesión con SQL sin formato.
jpmc26
180

Los objetos de sesión de SQL Alchemy tienen su propio executemétodo:

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

Todas las consultas de su aplicación deben pasar por un objeto de sesión, ya sean SQL sin formato o no. Esto asegura que las consultas sean gestionadas adecuadamente por una transacción , lo que permite que múltiples consultas en la misma solicitud se confirmen o reviertan como una sola unidad. Salir de la transacción utilizando el motor o la conexión lo pone en un riesgo mucho mayor de errores sutiles, posiblemente difíciles de detectar, que pueden dejarlo con datos corruptos. Cada solicitud debe asociarse con una sola transacción, y el uso db.sessiongarantizará que este sea el caso de su aplicación.

También tenga en cuenta que executeestá diseñado para consultas parametrizadas . Use parámetros, como :valen el ejemplo, para cualquier entrada a la consulta para protegerse de los ataques de inyección SQL. Puede proporcionar el valor de estos parámetros pasando un dictsegundo argumento, donde cada clave es el nombre del parámetro tal como aparece en la consulta. La sintaxis exacta del parámetro en sí puede ser diferente según su base de datos, pero todas las principales bases de datos relacionales los admiten de alguna forma.

Asumiendo que es una SELECTconsulta, esto devolverá un iterable de RowProxyobjetos.

Puede acceder a columnas individuales con una variedad de técnicas:

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, prefiero convertir los resultados en 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)

Si no está usando la extensión Flask-SQLAlchemy, aún puede usar fácilmente una sesión:

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})
jpmc26
fuente
Una selección devolverá un ResultProxy.
Alan B
@AlanB Sí. Elegí mal mis palabras cuando lo llamé secuencia, lo que implica que implementa el protocolo de secuencia. Lo he corregido y aclarado. Gracias.
jpmc26
@ jpmc26 debería cerrar la sesión después de ejecutar la consulta como db.session.close ()? ¿Y seguirá teniendo los beneficios de la agrupación de conexiones?
ravi malhotra
58

docs: Tutorial del lenguaje de expresiones SQL - Usar texto

ejemplo:

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')
Jake Berger
fuente
1
El enlace a los documentos de sqlalchemy parece estar desactualizado. Esto es más reciente: docs.sqlalchemy.org/en/latest/core/…
Carl
1
¿Puedo preguntar por qué estamos usando ==?
Nam G VU
1
@ Jake Berger muchas gracias por ti. He perdido casi un día en busca de esta respuesta. Estaba ejecutando directamente el sql sin convertirlo en texto. Fue un error de lanzamiento cada vez que tenemos% students% en mi cláusula where. Un gran aplauso por tu respuesta.
Suresh Kumar
1
@NamGVU porque, como en la mayoría de los lenguajes de programación, =normalmente está reservado para asignar un valor; mientras que ==está reservado para comparar valores
Jake Berger
2
@JakeBerger ¿Tienes un enlace para eso? SQL no es tal lenguaje, y a juzgar por los documentos de SQLAlchemy, esto no es así.
johndodo
36

Puede obtener los resultados de las consultas SELECT SQL utilizando from_statement()y text()como se muestra aquí . No tienes que lidiar con las tuplas de esta manera. Como ejemplo para una clase que Usertiene el nombre de la tabla usersque puede probar,

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

return user
TrigonaMinima
fuente
15
result = db.engine.execute(text("<sql here>"))

ejecuta el <sql here>pero no lo confirma a menos que esté en autocommitmodo. Por lo tanto, las inserciones y actualizaciones no se reflejarían en la base de datos.

Para comprometerse después de los cambios, haga

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

Esta es una respuesta simplificada sobre cómo ejecutar una consulta SQL desde Flask Shell

Primero, asigne su módulo (si su módulo / aplicación es manage.py en la carpeta principal y usted está en un sistema operativo UNIX), ejecute:

export FLASK_APP=manage

Ejecutar frasco shell

flask shell

Importar lo que necesitamos ::

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

Ejecute su consulta:

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

Esto utiliza la conexión de base de datos actual que tiene la aplicación.

Luigi Lopez
fuente
0

¿Has intentado usar connection.execute(text( <sql here> ), <bind params here> )y vincular parámetros como se describe en los documentos ? Esto puede ayudar a resolver muchos problemas de formato y rendimiento de parámetros. Tal vez el error de puerta de enlace es un tiempo de espera? Los parámetros de enlace tienden a hacer que las consultas complejas se ejecuten sustancialmente más rápido.

jhnwsk
fuente
2
Según los documentos , debería ser connection.execute(text(<sql here>), <bind params> ). bind paramsNO debería estar adentro text(). alimentación en los parámetros de enlace al método execute ()
Jake Berger
El enlace de Jake está roto. Creo que esta es la URL que es relevante ahora: docs.sqlalchemy.org/en/latest/core/…
code_dredd
-1

Si se quiere evitar tuplas, otra forma es llamando a los first, oneo allmétodos:

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

assert query.first().name == "Welcome to my blog"
Joe Gasewicz
fuente