Jak wykonać surowy SQL w aplikacji Flask-SQLAlchemy

219

Jak wykonać surowy SQL w SQLAlchemy?

Mam aplikacji sieci web Python, która działa na kolbie i interfejsów do bazy danych za pośrednictwem SQLAlchemy.

Potrzebuję sposobu na uruchomienie surowego SQL. Kwerenda obejmuje wiele połączeń tabel wraz z widokami Inline.

Próbowałem:

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

Ale ciągle pojawiają się błędy bramy.

starwing123
źródło
5
Patrzyłem na to wcześniej, ale nie mogłem znaleźć samouczka na temat uruchamiania aktualizacji. Wolałbym też nie uczyć się składni i ukrywać dość długie (około 20 wierszy) zapytanie SQL.
starwing123
103
@MarkusUnterwaditzer Myślałem o tym, ale teraz zdecydowanie się nie zgadzam. Surowy, odpowiednio sparametryzowany SQL jest ogólnie znacznie łatwiejszy do odczytania i obsługi niż kilka wywołań funkcji i obiektów, które go generują. Zapewnia również pełne możliwości bazy danych bez konieczności przeskakiwania przez obręcze, aby ORM wygenerował poprawną składnię (jeśli to możliwe) i powstrzymuje ORM przed robieniem nieoczekiwanych rzeczy. Możesz zadać pytanie: „Dlaczego więc w ogóle korzystać z SQLAlchemy?”, A jedyną odpowiedzią, jaką mam, jest: „Istniejąca aplikacja używa go, a zmiana wszystkiego jest zbyt droga”.
jpmc26
4
@ jpmc26 Podniosłem twój komentarz - jako miłośnik SQL mam trudności z pomysłem „oddania kluczy do bazy danych” nieodpowiedzialnemu alchemikowi i skłonności do pochylania się po stronie ORM jest antypatternem :) powiedział, że chciałbym przyspieszyć niektóre komponenty, takie jak rejestracja / zarządzanie użytkownikami, a także generowanie tabel z sekwencjami przycisków, dla których mogę kodować działania + SQL. Czy natknąłeś się na narzędzia przyjazne sceptycznie dla ORM, które działają dobrze w środowisku Python?
zx81 17.07.15
@ jpmc26 Czego używasz w środowisku Python, aby używać tylko SQL lub całkiem blisko jak C # Dapper? Wszystko, co widzę w frameworku internetowym Python, wymaga ode mnie użycia SQLAlchemy, a ja nie lubię ORM, a jeśli go używam, jest to niezwykle minimalne.
Johnny
@ johnny Nie miałem okazji tego spróbować, ale biblioteki połączeń surowej bazy danych są prawdopodobnie wystarczające. Na przykład psycopg2 ma zwracane kursory namedtuplei dictbezpośrednio: initd.org/psycopg/docs/extras.html .
jpmc26,

Odpowiedzi:

310

Czy próbowałeś:

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

lub:

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
źródło
7
Jeśli wstawisz lub zaktualizujesz, w jaki sposób dokonujesz transakcji?
David S
14
Jeśli używasz surowego SQL, to kontrolujesz transakcje, więc musisz samodzielnie wydawać instrukcje BEGINi COMMIT.
Miguel
1
Czy te same polecenia SQL działają po wydaniu ich bez SQLAlchemy? Możesz włączyć debugowanie w bazie danych, aby zobaczyć, jakie polecenia wykonuje.
Miguel
27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))wykonuje to i zatwierdza.
Devi
8
@Miguel „Jeśli używasz surowego SQL, kontrolujesz transakcje, więc musisz samodzielnie wydawać instrukcje BEGIN i COMMIT”. To po prostu nieprawda. Możesz użyć surowego SQL z obiektem sesji. Właśnie zauważyłem ten komentarz, ale możesz zobaczyć moją odpowiedź na temat używania sesji z surowym SQL.
jpmc26,
180

Obiekty sesji Alchemia SQL mają własną executemetodę:

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

Wszystkie zapytania aplikacji powinny przechodzić przez obiekt sesji, bez względu na to, czy jest to surowy SQL, czy nie. Zapewnia to prawidłowe zarządzanie zapytaniami przez transakcję , co pozwala na zatwierdzenie lub wycofanie wielu zapytań w tym samym żądaniu jako pojedynczej jednostki. Wyjście poza transakcję za pomocą silnika lub połączenia naraża Cię na znacznie większe ryzyko subtelnych, być może trudnych do wykrycia błędów, które mogą spowodować uszkodzenie danych. Każde żądanie powinno być powiązane tylko z jedną transakcją, a użycie db.sessionzapewni, że tak będzie w przypadku Twojej aplikacji.

Weź również pod uwagę, że executejest przeznaczony do sparametryzowanych zapytań . Użyj parametrów, takich jak :valw przykładzie, do wszelkich danych wejściowych do zapytania, aby uchronić się przed atakami iniekcyjnymi SQL. Możesz podać wartość tych parametrów, przekazując dictjako drugi argument, gdzie każdy klucz jest nazwą parametru wyświetlaną w zapytaniu. Dokładna składnia samego parametru może być różna w zależności od bazy danych, ale wszystkie główne relacyjne bazy danych obsługują je w jakiejś formie.

Zakładając, że jest to SELECTzapytanie, zwróci iterowalną liczbę RowProxyobiektów.

Możesz uzyskać dostęp do poszczególnych kolumn za pomocą różnych technik:

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

Osobiście wolę przekonwertować wyniki na 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)

Jeśli nie używasz rozszerzenia Flask-SQLAlchemy, nadal możesz łatwo użyć sesji:

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
źródło
Opcja Select zwróci ResultProxy.
Alan B
@AlanB Tak. Źle wybrałem słowa, gdy nazwałem je sekwencją, co sugeruje, że implementuje protokół sekwencji. Poprawiłem i wyjaśniłem. Dzięki.
jpmc26,
@ jpmc26 powinien zamknąć sesję po wykonaniu zapytania, np. db.session.close ()? I czy nadal będzie miał zalety puli połączeń?
ravi malhotra
58

docs: Samouczek języka SQL Expression - Korzystanie z tekstu

przykład:

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
źródło
1
Link do dokumentów sqlalchemy wydaje się być nieaktualny. To jest nowsze: docs.sqlalchemy.org/en/latest/core/…
Carl
1
Czy mogę zapytać, dlaczego używamy ==?
Nam G VU
1
@Jake Berger wielkie dzięki za ciebie. Zmarnowałem prawie dzień na poszukiwanie tej odpowiedzi. Właśnie bezpośrednio wykonywałem sql bez konwersji na tekst. Zgłaszał błąd, ilekroć mamy% studentów% w mojej klauzuli where. Wielkie brawa za twoją odpowiedź.
Suresh Kumar
1
@NamGVU, ponieważ podobnie jak w większości języków programowania, =zwykle jest zarezerwowany do przypisywania wartości; mając na uwadze, że ==jest zarezerwowany do porównywania wartości
Jake Berger
2
@JakeBerger Czy masz do tego link? SQL nie jest takim językiem, a sądząc po dokumentach SQLAlchemy, tak nie jest.
johndodo
36

Możesz uzyskać wyniki zapytań SELECT SQL za pomocą from_statement()i text()jak pokazano tutaj . W ten sposób nie musisz radzić sobie z krotkami. Jako przykład dla klasy Usero nazwie tabeli, usersktórą możesz wypróbować,

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

return user
Trigona Minima
źródło
15
result = db.engine.execute(text("<sql here>"))

wykonuje, <sql here>ale go nie zatwierdza, chyba że jesteś w autocommittrybie. Tak więc wstawki i aktualizacje nie odzwierciedlają się w bazie danych.

Aby zatwierdzić po zmianach, wykonaj

result = db.engine.execute(text("<sql here>").execution_options(autocommit=True))
Devi
źródło
2

Jest to uproszczona odpowiedź na pytanie, jak uruchomić zapytanie SQL z Flask Shell

Najpierw zamapuj moduł (jeśli Twój moduł / aplikacja jest manage.py w folderze głównym, a jesteś w systemie operacyjnym UNIX), uruchom:

export FLASK_APP=manage

Uruchom pocisk Flask

flask shell

Zaimportuj to, czego potrzebujemy:

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

Uruchom zapytanie:

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

Wykorzystuje to obecnie połączenie z bazą danych, które ma aplikację.

Luigi Lopez
źródło
0

Czy próbowałeś używać connection.execute(text( <sql here> ), <bind params here> )i wiązać parametry zgodnie z opisem w dokumentacji ? Pomoże to rozwiązać wiele problemów z formatowaniem parametrów i wydajnością. Być może błąd bramy przekroczył limit czasu? Parametry wiązania powodują, że złożone zapytania są wykonywane znacznie szybciej.

jhnwsk
źródło
2
zgodnie z dokumentami powinno być connection.execute(text(<sql here>), <bind params> ). bind paramsNIE powinno być w text(). wprowadzanie parametrów wiązania do metody execute ()
Jake Berger
Link Jake'a jest zepsuty. Myślę, że ten adres URL jest teraz aktualny
code_dredd
-1

Jeśli chcesz uniknąć krotki, innym sposobem jest wywołanie first, oneani allmetod:

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

assert query.first().name == "Welcome to my blog"
Joe Gasewicz
źródło