如何在Flask-SQLAlchemy应用中执行原始SQL


218

如何在SQLAlchemy中执行原始SQL?

我有一个在烧瓶上运行的python Web应用程序,并通过SQLAlchemy连接到数据库。

我需要一种运行原始SQL的方法。该查询涉及多个表联接以及内联视图。

我试过了:

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

但是我不断收到网关错误。


5
我之前已经看过,但是找不到运行更新的教程。我也不想学习语法,而是隐蔽较长(约20行)的SQL查询。
2013年

103
@MarkusUnterwaditzer我以前曾经这么认为,但是现在我强烈不同意。通常,原始的,经过适当参数化的SQL比一堆生成它的函数调用和对象更易于阅读和维护。它还为您提供了数据库的全部功能,而不必花很多时间来使ORM生成正确的语法(如果可能的话),并使ORM​​避免发生意外的事情。您可能会问一个问题:“那为什么还要使用SQLAlchemy?”,而我唯一的回答是:“现有的应用程序使用它并且更改所有内容都太昂贵了。”
jpmc26 2014年

4
@ jpmc26补充您的评论-作为SQL爱好者,我对“不负责任的炼金术士”放弃数据库的密钥”这个想法感到很困难,并且倾向于依靠ORM是一种反模式 :)我说我很想加速某些组件,例如用户注册/管理,以及带有按钮序列的表的生成,我可以为这些按钮编码操作+ SQL。您是否遇到过一些对ORM持怀疑态度的工具,这些工具在Python框架中很适合您?
zx81

@ jpmc26您在Python框架中使用什么来仅使用SQL或与C#Dapper相当接近?我在Python Web框架中看到的所有内容都希望我使用SQLAlchemy,而且我不喜欢ORM,而且如果我使用ORM,那么它是极少的。
约翰尼

@johnny我自己没有机会尝试,但是原始数据库连接库可能就足够了。例如,psycopg2具有光标是回报namedtupledict直接:initd.org/psycopg/docs/extras.html
jpmc26 2015年

Answers:


309

你有没有尝试过:

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

要么:

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
如果您进行插入或更新,则如何提交事务?
David S

14
如果使用的是原始SQL,则可以控制事务,因此必须自己发出BEGINand COMMIT语句。
Miguel

1
在不使用SQLAlchemy的情况下发出相同的SQL命令是否有效?您可能要在数据库上启用调试,以便可以查看数据库正在执行什么命令。
米格尔2014年

27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))执行并提交它。
德维(Devi)2015年

8
@Miguel“如果使用原始SQL,则可以控制事务,因此必须自己发出BEGIN和COMMIT语句。” 这是不正确的。您可以将原始SQL与会话对象一起使用。刚刚注意到了此评论,但是您可以看到我的答案,如何在原始SQL中使用会话。
jpmc26 2015年

180

SQL Alchemy会话对象具有自己的execute方法:

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

您的所有应用程序查询都应通过会话对象,无论它们是否是原始SQL。这样可以确保由事务适当地管理查询,该事务允许将同一请求中的多个查询作为一个单元提交或回滚。使用引擎连接进行事务处理将使您面临更大的隐患,即可能很难检测到可能导致数据损坏的错误的风险。每个请求应仅与一个事务相关联,并且使用db.session可以确保您的应用程序是这种情况。

另请注意,它execute是为参数化查询设计的。使用:val示例中的参数作为查询的任何输入,以保护自己免受SQL注入攻击。您可以通过传递a dict作为第二个参数来提供这些参数的值,其中每个键都是在查询中显示的参数名称。参数本身的确切语法可能会有所不同,具体取决于您的数据库,但是所有主要的关系数据库都以某种形式支持它们。

假设这是一个SELECT查询,它将返回一个可迭代RowProxy对象。

您可以使用多种技术访问各个列:

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

就个人而言,我更喜欢将结果转换为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)

如果您不使用Flask-SQLAlchemy扩展,您仍然可以轻松使用会话:

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})

Select将返回一个ResultProxy。
艾伦B

@AlanB是的。当我称它为序列时,我选择的话很不好,暗示它实现了序列协议。我已更正并澄清。谢谢。
jpmc26

@ jpmc26应该在执行类似db.session.close()的查询后关闭会话吗?它将仍然具有连接池的好处吗?
拉维·马尔霍特拉

58

docs:SQL表达式语言教程-使用文本

例:

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
到sqlalchemy文档的链接似乎已过期。这是最新的信息:docs.sqlalchemy.org/en/latest/core/…–
卡尔

1
请问为什么要用==
Nam G VU

1
@杰克·伯杰(Jake Berger)非常感谢您。我已经浪费了将近一天的时间来寻找这个答案。我只是直接执行sql而不转换为文本。每当我们的where子句中有%students%时,就会抛出错误。为您的回答鼓掌。
Suresh Kumar

1
@NamGVU,因为像大多数编程语言一样,=通常保留用于分配值;而==保留用于比较
Jake Berger '18

2
@JakeBerger你有一个链接吗?SQL不是这种语言,根据SQLAlchemy文档判断,事实并非如此。
约翰多多

36

你可以使用SELECT SQL查询的结果from_statement(),并text()如图所示这里。您不必以这种方式处理元组。作为User具有表名的类的示例,users您可以尝试,

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>"))

<sql here>除非您处于autocommit模式,否则执行但不提交。因此,插入和更新不会反映在数据库中。

要在更改后提交,请执行

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

2

这是如何从Flask Shell运行SQL查询的简化答案

首先,映射您的模块(如果您的模块/应用程序是principal文件夹中的manage.py,并且您在UNIX操作系统中),请运行:

export FLASK_APP=manage

运行烧瓶壳

flask shell

导入我们需要的::

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

运行查询:

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

这将使用具有应用程序的当前数据库连接。


0

您是否尝试过按照文档connection.execute(text( <sql here> ), <bind params here> )所述使用和绑定参数?这可以帮助解决许多参数格式设置和性能问题。也许网关错误是超时?绑定参数往往会使复杂的查询执行得更快。


2
根据文档,应该是connection.execute(text(<sql here>), <bind params> )bind params不应该在中text()将绑定参数输入到execute()方法中
Jake Berger 2013年

杰克的链接已断开。我认为这是现在相关的URL:docs.sqlalchemy.org/en/latest/core/…–
code_dredd

-1

如果你想避免的元组,另一种方式是通过调用firstoneall方法:

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

assert query.first().name == "Welcome to my blog"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.