Как выполнить сырой SQL в приложении Flask-SQLAlchemy


219

Как вы выполняете сырой SQL в SQLAlchemy?

У меня есть веб-приложение на Python, которое работает на колбе и взаимодействует с базой данных через SQLAlchemy.

Мне нужен способ запустить сырой SQL. Запрос включает в себя несколько объединений таблиц наряду с встроенными представлениями.

Я пробовал:

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

Но я продолжаю получать ошибки шлюза.


5
Я смотрел на это раньше, но не смог найти учебник по запуску обновления. Я также предпочел бы не изучать синтаксис и скрывать довольно длинный (около 20 строк) SQL-запрос.
starwing123

103
@MarkusUnterwaditzer Раньше я так думал, но сейчас я категорически не согласен. Необработанный, правильно параметризованный SQL, как правило, гораздо легче читать и поддерживать, чем набор вызовов функций и объектов, которые его генерируют. Он также предоставляет вам все возможности базы данных без необходимости перепрыгивать через обручи, чтобы заставить ORM генерировать правильный синтаксис (если это вообще возможно), и удерживает ORM от непредвиденных действий. Вы можете задать вопрос: «Тогда зачем вообще использовать SQLAlchemy?», И единственный ответ, который у меня есть, - «Существующее приложение использует его, и изменение всего слишком дорого».
jpmc26

4
@ jpmc26 Подняв комментарий, я, как любитель SQL, испытываю трудности с идеей «раздавать ключи к базе данных» безответственному алхимику и склоняюсь на сторону ORM - это антипаттерн :) Я сказал, что хотел бы ускорить определенные компоненты, такие как регистрация / управление пользователями, а также создание таблиц с последовательностями кнопок, для которых я могу кодировать действия + SQL. Вы сталкивались с некоторыми ORM-скептически дружественными инструментами, которые хорошо работают для вас в среде Python?
zx81

@ jpmc26 Что вы используете в среде Python для использования только SQL или довольно близко, как C # Dapper? Все, что я вижу в веб-фреймворке Python, хочет, чтобы я использовал SQLAlchemy, и мне не нравится ORM, и если я его использую, он крайне минимален.
джонни

@johnny У меня не было возможности попробовать это самому, но сырых библиотек подключения к базе данных, вероятно, достаточно. Например, у psycopg2 есть курсоры, которые возвращаются namedtupleи dictнапрямую: initd.org/psycopg/docs/extras.html .
jpmc26

Ответы:


310

Ты пробовала:

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
Если вы делаете вставку или обновление, как вы фиксируете транзакцию?
Дэвид С.

14
Если вы используете необработанный SQL, тогда вы управляете транзакциями, поэтому вы должны самостоятельно выполнить операторы BEGINи COMMIT.
Мигель

1
Работают ли те же команды SQL, когда вы запускаете их без SQLAlchemy? Возможно, вы захотите включить отладку в вашей базе данных, чтобы вы могли видеть, какие команды она выполняет.
Мигель

27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))выполняет и фиксирует это тоже.
Деви

8
@Miguel "Если вы используете сырой SQL, тогда вы управляете транзакциями, поэтому вы должны сами выполнить операторы BEGIN и COMMIT." Это просто неправда. Вы можете использовать сырой SQL с объектом сеанса. Только что заметил этот комментарий, но вы можете увидеть мой ответ о том, как использовать сеанс с сырым SQL.
jpmc26

180

Объекты сеанса SQL Alchemy имеют свой собственный executeметод:

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

Все запросы вашего приложения должны проходить через объект сеанса, независимо от того, являются ли они необработанным SQL или нет. Это обеспечивает правильное управление запросами с помощью транзакции , что позволяет фиксировать или откатывать несколько запросов в одном запросе как один блок. Выход за пределы транзакции с использованием механизма или соединения подвергает вас гораздо большему риску скрытых, возможно трудных для обнаружения ошибок, которые могут оставить вас с поврежденными данными. Каждый запрос должен быть связан только с одной транзакцией, и использование db.sessionгарантирует, что это относится к вашему приложению.

Также обратите внимание, что executeон предназначен для параметризованных запросов . Используйте параметры, как :valв примере, для любых входных данных запроса, чтобы защитить себя от атак SQL-инъекций. Вы можете предоставить значение для этих параметров, передав в dictкачестве второго аргумента a , где каждый ключ - это имя параметра в том виде, в каком оно появляется в запросе. Точный синтаксис самого параметра может отличаться в зависимости от вашей базы данных, но все основные реляционные базы данных поддерживают их в той или иной форме.

Если предположить , что это 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})

Выбор вернет ResultProxy.
Алан Б

@AlanB Да. Я плохо выбрал свои слова, когда назвал это последовательностью, подразумевая, что это реализует протокол последовательности. Я исправил и уточнил. Спасибо.
jpmc26

@ jpmc26 должен закрывать сессию после выполнения запроса типа db.session.close ()? И будут ли у него все еще преимущества пула соединений?
Рави Малхотра

58

документы: Учебник по языку выражений 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
Могу ли я спросить, почему мы используем ==?
Нам Г ВУ

1
@ Джейк Бергер большое спасибо за тебя. Я потратил почти день в поисках этого ответа. Я просто непосредственно выполнял sql без преобразования в текст. Он выдавал ошибку всякий раз, когда в моем предложении where было% студентов%. Большие аплодисменты за ваш ответ.
Суреш Кумар

1
@NamGVU, потому что, как и в большинстве языков программирования, =обычно резервируется для присвоения значения; тогда как ==зарезервировано для сравнения значений
Джейк Бергер

2
@JakeBerger У вас есть ссылка для этого? SQL не такой язык, и, судя по документации по SQLAlchemy, это не так.
Johndodo

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

Это упрощенный ответ о том, как запустить SQL-запрос из Flask Shell

Во-первых, сопоставьте ваш модуль (если ваш модуль / приложение - файл 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 ()
Джейк Бергер,

Ссылка Джейка не работает. Я думаю, что это URL, который сейчас актуален: docs.sqlalchemy.org/en/latest/core/…
code_dredd

-1

Если вы хотите , чтобы избежать кортежи, другой путь, вызывая first, oneили allметоды:

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

assert query.first().name == "Welcome to my blog"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.