Как выполнить сырой 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.