Formatowanie ciągów zapytań SQL w języku Python

100

Próbuję znaleźć najlepszy sposób formatowania ciągu zapytania sql. Kiedy debuguję moją aplikację, chciałbym rejestrować wszystkie ciągi zapytań sql i ważne jest, aby łańcuch był odpowiednio sformatowany.

opcja 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • Jest to dobre do drukowania ciągu sql.
  • Nie jest to dobre rozwiązanie, jeśli łańcuch jest długi i nie mieści się w standardowej szerokości 80 znaków.

Opcja 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Tutaj kod jest jasny, ale kiedy drukujesz ciąg zapytania sql, otrzymujesz wszystkie te irytujące białe spacje.

    u '\ nwybierz pole1, pole2, pole3, pole4 \ n_ _ ___ z tabeli \ n _ ___ gdzie warunek1 = 1 \ n _ ___ _ i warunek2 = 2'

Uwaga: białe spacje zastąpiłem podkreśleniem _, ponieważ zostały one przycięte przez edytor

Wariant 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Nie podoba mi się ta opcja, ponieważ narusza przejrzystość dobrze sklasyfikowanego kodu.

Opcja 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • Nie podoba mi się ta opcja, ponieważ całe dodatkowe wpisywanie w każdym wierszu i trudno jest również edytować zapytanie.

Dla mnie najlepszym rozwiązaniem byłaby opcja 2, ale nie lubię dodatkowych białych znaków podczas drukowania ciągu sql.

Czy znasz inne opcje?

ssoler
źródło
To jest to, co ludzie Psycopg nazywają naiwnym podejściem do składu ciągów zapytań, np. Przy użyciu konkatenacji ciągów - initd.org/psycopg/docs/… . Zamiast tego należy używać parametrów zapytania, aby uniknąć ataków typu SQL injection i automatycznie konwertować obiekty Pythona do iz literałów SQL. stackoverflow.com/questions/3134691/…
Matthew Cornell
To pytanie w rzeczywistości nie jest specyficzne dla zapytań SQL, ale odnosi się ogólnie do formatowania ciągów wieloliniowych w Pythonie. Należy usunąć znacznik SQL.
cstork

Odpowiedzi:

135

Przepraszam, że piszę w tak starym wątku - ale jako ktoś, kto podziela pasję do Pythonowego „best”, pomyślałem, że podzielę się naszym rozwiązaniem.

Rozwiązaniem jest zbudowanie instrukcji SQL przy użyciu technologii String Literal Concatenation w języku Python ( http://docs.python.org/ ), która może być zakwalifikowana gdzieś pomiędzy Opcją 2 a Opcją 4

Przykład kodu:

sql = ("SELECT field1, field2, field3, field4 "
       "FROM table "
       "WHERE condition1=1 "
       "AND condition2=2;")

Działa również z f-stringami :

fields = "field1, field2, field3, field4"
table = "table"
conditions = "condition1=1 AND condition2=2"

sql = (f"SELECT {fields} "
       f"FROM {table} "
       f"WHERE {conditions};")

Plusy:

  1. Zachowuje pythonowy format „dobrze tabelaryczny”, ale nie dodaje zbędnych znaków spacji (które zanieczyszczają logowanie).
  2. Pozwala uniknąć brzydoty kontynuacji odwrotnego ukośnika opcji 4, co utrudnia dodawanie stwierdzeń (nie wspominając o ślepocie na białe znaki).
  3. Co więcej, bardzo łatwo jest rozwinąć instrukcję w VIM (po prostu umieść kursor w punkcie wstawiania i naciśnij SHIFT-O, aby otworzyć nową linię).
user590028
źródło
1
Jeśli to jest do drukowania, myślę, że lepszą alternatywą jest zapisanie go jako mutiline string """i użycie go textwrap.dedent()przed
wydrukowaniem
Grałem z tą opcją, ale powodowało to również wielowierszowe wyjście dziennika. Podczas śledzenia aplikacji db chatty powodowało to obszerne dane wyjściowe.
user590028
1
To stary wątek, ale używam tego formatu jako najlepszej praktyki, jednak przy dłuższych zapytaniach staje się nudny
Jabda
8
Czy nie powinniśmy zawsze używać podwójnych cudzysłowów, "sql query"aby uniknąć bałaganu w ciągach SQL (które standardowo używają apostrofów)?
tpvasconcelos
19

Oczywiście rozważałeś wiele sposobów na napisanie kodu SQL w taki sposób, aby był poprawnie drukowany, ale co powiesz na zmianę instrukcji „print” używanej do logowania do debugowania, zamiast pisania kodu SQL w sposób, którego nie lubisz? Korzystając ze swojej ulubionej opcji powyżej, co powiesz na funkcję rejestrowania, taką jak ta:

def debugLogSQL(sql):
     print ' '.join([line.strip() for line in sql.splitlines()]).strip()

sql = """
    select field1, field2, field3, field4
    from table"""
if debug:
    debugLogSQL(sql)

To również uczyniłoby trywialnym dodanie dodatkowej logiki w celu podzielenia zarejestrowanego ciągu na wiele linii, jeśli linia jest dłuższa niż żądana długość.

cdlk
źródło
11

Najczystszy sposób, z jakim się spotkałem, jest inspirowany przewodnikiem po stylu sql .

sql = """
    SELECT field1, field2, field3, field4
      FROM table
     WHERE condition1 = 1
       AND condition2 = 2;
"""

Zasadniczo słowa kluczowe rozpoczynające klauzulę powinny być wyrównane do prawej, a nazwy pól itp. Powinny być wyrównane do lewej. Wygląda to bardzo schludnie i jest również łatwiejsze do debugowania.

aandis
źródło
2
sql = ("select field1, field2, field3, field4 "
       "from table "
       "where condition1={} "
       "and condition2={}").format(1, 2)

Output: 'select field1, field2, field3, field4 from table 
         where condition1=1 and condition2=2'

jeśli wartość warunku powinna być ciągiem znaków, możesz zrobić tak:

sql = ("select field1, field2, field3, field4 "
       "from table "
       "where condition1='{0}' "
       "and condition2='{1}'").format('2016-10-12', '2017-10-12')

Output: "select field1, field2, field3, field4 from table where
         condition1='2016-10-12' and condition2='2017-10-12'"
pangpang
źródło
5
Proszę, nigdy tego nie rób. Nazywa się to wstrzyknięciem SQL i jest naprawdę niebezpieczne. Prawie każda biblioteka bazy danych Pythona zapewnia możliwość używania parametrów. Jeśli złapiesz się na używaniu format()ciągów SQL, jest to główny zapach kodu.
mattmc3
Myślę, że nie możemy go użyć, musisz sprawdzić parametry przed jego użyciem i powinieneś wiedzieć, co zdajesz.
pangpang
Walidacja jest o wiele bardziej podatna na błędy niż zwykłe używanie where condition1=:field1i przekazywanie wartości jako parametrów. Jeśli używasz .format(), będzie sposób na wstawienie a ';DROP TABLE Usersdo twojego SQL. Spójrz na PEP-249, aby dowiedzieć się, jak poprawnie używać parametrów. python.org/dev/peps/pep-0249/#paramstyle
mattmc3
1

Możesz użyć, inspect.cleandocaby ładnie sformatować wydrukowaną instrukcję SQL.

Działa to bardzo dobrze z opcją 2 .

Uwaga: print("-"*40)służy to tylko zademonstrowaniu zbędnych pustych wierszy, jeśli nie używasz cleandoc.

from inspect import cleandoc
def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2
    """

    print("-"*40)
    print(sql)
    print("-"*40)
    print(cleandoc(sql))
    print("-"*40)

query()

Wynik:

----------------------------------------

        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2

----------------------------------------
select field1, field2, field3, field4
from table
where condition1=1
and condition2=2
----------------------------------------

Z dokumentów :

inspect.cleandoc (doc)

Usuń wcięcia z ciągów dokumentacyjnych, które są wcięte, aby wyrównać je z blokami kodu.

Wszystkie wiodące spacje są usuwane z pierwszego wiersza. Wszelkie wiodące spacje, które można równomiernie usunąć od drugiego wiersza, są usuwane. Puste linie na początku i na końcu są następnie usuwane. Ponadto wszystkie karty są rozszerzane do spacji.

Mike Scotty
źródło
0

Myślę, że aby całkowicie uniknąć formatowania , dobrym rozwiązaniem jest użycie procedur .

Wywołanie procedury daje wynik dowolnego zapytania, które chcesz umieścić w tej procedurze. W rzeczywistości możesz przetwarzać wiele zapytań w ramach procedury. Wywołanie zwróci tylko ostatnie wywołane zapytanie .

MYSQL

DROP PROCEDURE IF EXISTS example;
 DELIMITER //
 CREATE PROCEDURE example()
   BEGIN
   SELECT 2+222+2222+222+222+2222+2222 AS this_is_a_really_long_string_test;
   END //
 DELIMITER;

#calling the procedure gives you the result of whatever query you want to put in this procedure. You can actually process multiple queries within a procedure. The call just returns the last query result
 call example;

Pyton

sql =('call example;')
Paroofkey
źródło
-1

możesz umieścić nazwy pól w tablicy „pola”, a następnie:


sql = 'select %s from table where condition1=1 and condition2=2' % (
 ', '.join(fields))
jcomeau_ictx
źródło
jeśli twoja lista warunków rośnie, możesz zrobić to samo, używając 'i' .join (warunki)
jcomeau_ictx
z Twoim rozwiązaniem zapytanie byłoby jeszcze trudniejsze do edycji niż w przypadku Option_4, a także byłoby trudne do odczytania.
ssoler
@ssoler, to zależy od tego, jak się coś robi. Deklaruję kilka zmiennych w swoich programach i zamiast tego używam tablic łańcuchów, co sprawia, że ​​metody takie jak powyższe są bardzo przydatne i łatwe w utrzymaniu, przynajmniej przeze mnie.
jcomeau_ictx
-1

Sugerowałbym trzymać się opcji 2 (zawsze używam jej do zapytań bardziej złożonych niż SELECT * FROM table) i jeśli chcesz wydrukować ją ładnie, zawsze możesz użyć osobnego modułu .

Michał Chruszcz
źródło
-1

W przypadku krótkich zapytań, które mieszczą się w jednym lub dwóch wierszach, używam rozwiązania literału ciągu w powyższym rozwiązaniu z najwyższym głosowaniem. W przypadku dłuższych zapytań dzielę je na .sqlpliki. Następnie używam funkcji opakowania, aby załadować plik i wykonać skrypt, na przykład:

script_cache = {}
def execute_script(cursor,script,*args,**kwargs):
    if not script in script_cache:
        with open(script,'r') as s:
            script_cache[script] = s
    return cursor.execute(script_cache[script],*args,**kwargs)

Oczywiście to często znajduje się w klasie, więc zwykle nie muszę zdawać tego cursorwprost. Generalnie używam codecs.open(), ale to przenosi ogólną ideę. Następnie skrypty SQL są całkowicie samodzielne w swoich własnych plikach z własnym podświetlaniem składni.

Aikon
źródło
-2
sql = """\
select field1, field2, field3, field4
from table
where condition1=1
and condition2=2
"""

[edytuj w odpowiedzi na komentarz]
Posiadanie ciągu SQL wewnątrz metody NIE oznacza, że ​​musisz go „tabulować”:

>>> class Foo:
...     def fubar(self):
...         sql = """\
... select *
... from frobozz
... where zorkmids > 10
... ;"""
...         print sql
...
>>> Foo().fubar()
select *
from frobozz
where zorkmids > 10
;
>>>
John Machin
źródło
IMO to jest to samo, co Option_2
ssoler
@ssoler: Twoja Option_2 ma początkowe spacje we wszystkich wierszach; Zauważ, że twój przykład pomija wcześniejsze spacje select. W mojej odpowiedzi nie ma spacji wiodących. Co skłoniło cię do sformułowania opinii, że są takie same?
John Machin
Jeśli umieścisz łańcuch sql wewnątrz metody, będziesz musiał zestawić wszystkie wiersze w tabeli (Option_2). Jednym z możliwych rozwiązań jest Option_3.
ssoler
@ssoler: Przepraszam, nie rozumiem tej uwagi. Proszę spojrzeć na moją zaktualizowaną odpowiedź.
John Machin
Twoja zaktualizowana odpowiedź to moja Option_3, prawda? Nie podoba mi się ta opcja, ponieważ narusza przejrzystość dobrze sklasyfikowanego kodu.
ssoler