Форматування рядка запитів на Python SQL


93

Я намагаюся знайти найкращий спосіб форматування рядка запиту sql. Коли я налагоджую свою програму, я хотів би зареєструвати файл усіх рядків запиту sql, і важливо, щоб рядок був правильно сформований.

Варіант 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • Це добре для друку рядка sql.
  • Це не найкраще рішення, якщо рядок довгий і не відповідає стандартній ширині 80 символів.

Варіант 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Тут код зрозумілий, але при друці рядка запиту sql ви отримуєте всі ці надокучливі пробіли.

    u '\ nвиберіть поле1, поле2, поле3, поле4 \ n_ _ ___ із таблиці \ n _ ___, де умова1 = 1 \ n _ ___ _і умова2 = 2'

Примітка: Я замінив пробіли на підкреслення _, оскільки вони оброблені редактором

Варіант 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Мені не подобається цей параметр, оскільки він порушує чіткість добре табличного коду.

Варіант 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • Мені не подобається цей параметр, тому що всі зайві введення в кожному рядку і їх важко також редагувати.

Для мене найкращим рішенням буде варіант 2, але мені не подобаються зайві пробіли, коли я друкую рядок sql.

Чи знаєте ви ще якісь варіанти?


Це те, що люди у Psycopg називають наивним підходом до композиції рядків запитів, наприклад, за допомогою конкатенації рядків - initd.org/psycopg/docs/… . Натомість використовуйте параметри запиту, щоб уникнути атак SQL-ін’єкцій та для автоматичного перетворення об’єктів Python в і з літералів SQL. stackoverflow.com/questions/3134691/…
Метью Корнелл

Це питання насправді не є специфічним для запитів SQL, але стосується загалом форматування багаторядкових рядків у Python. Тег SQL слід видалити.
крейк

Відповіді:


130

Вибачте за допис у такій старій темі - але як хтось, хто також поділяє пристрасть до пітонічного "найкращого", я думав поділитися нашим рішенням.

Рішення полягає в тому, щоб побудувати оператори SQL, використовуючи Python String Literal Concatenation ( http://docs.python.org/ ), який можна кваліфікувати десь між Варіантом 2 і Варіантом 4

Зразок коду:

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

Працює також з f-струнами :

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

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

Плюси:

  1. Він зберігає пітонічний "добре табличний" формат, але не додає сторонні символи пробілу (що забруднює реєстрацію).
  2. Це дозволяє уникнути потворності зворотної косої риси Варіанту 4, що ускладнює додавання висловлювань (не кажучи вже про сліпий пробіл).
  3. І далі, дуже просто розгорнути оператор у VIM (просто наведіть курсор на точку вставки та натисніть SHIFT-O, щоб відкрити новий рядок).

1
Якщо це для друку, я вважаю, що кращою альтернативою є написання його як мутилінійного рядка з """і використання textwrap.dedent()перед виходом
slezica

Я грав із цим варіантом, але це також зробило багаторядковий вихід журналу. Під час відстеження дб-балакучого додатка це спричинило об’ємний вихід.
user590028

1
Це стара тема, але я використовував цей формат як найкращу практику, однак при довших запитах це втомлює
Джабда,

7
Чи не слід завжди використовувати подвійні лапки, "sql query"щоб уникнути возиння з рядками SQL (які використовують одинарні лапки як стандарт)?
tpvasconcelos

19

Ви, очевидно, розглядали безліч способів написати SQL таким чином, щоб він друкувався нормально, але як щодо того, щоб змінити оператор "print", який ви використовуєте для ведення журналу налагодження, а не писати свій SQL так, як вам не подобається? Використовуючи ваш улюблений варіант вище, як щодо функції ведення журналу, наприклад, такої:

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)

Це також зробило б тривіальним додавання додаткової логіки для розподілу зареєстрованого рядка на кілька рядків, якщо рядок довший за бажану довжину.


11

Найчистіший спосіб, з яким я стикався, натхненний посібником зі стилю sql .

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

По суті, ключові слова, що починають речення, мають бути вирівняні по правому краю, а імена полів тощо - по лівому краю. Це виглядає дуже акуратно, і його також легше налагодити.


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'

якщо значення умови має бути рядком, ви можете зробити так:

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

5
Будь ласка, ніколи цього не роби. Це називається SQL-ін’єкцією, і це дійсно небезпечно. Практично кожна бібліотека баз даних Python забезпечує можливість використання параметрів. Якщо ви ловите себе за format()допомогою рядків SQL, це основний запах коду.
mattmc3

Я не думаю, що ми не можемо ним скористатися, перед його використанням потрібно перевірити параметри, і ви повинні знати, що передаєте.
pangpang

Перевірка набагато більше схильна до помилок, ніж просто використання, where condition1=:field1а потім передача значень як параметрів. Якщо ви використовуєте .format(), там буде спосіб вставити ';DROP TABLE Usersваш SQL. Подивіться на PEP-249, як правильно використовувати параметри. python.org/dev/peps/pep-0249/#paramstyle
mattmc3

0

Щоб уникнути форматування повністю , я вважаю чудовим рішенням використання процедур .

Виклик процедури дає результат будь-якого запиту, який ви хочете поставити в цій процедурі. Ви можете фактично обробити декілька запитів у рамках процедури. Виклик просто поверне останній запит, який був викликаний.

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;

Python

sql =('call example;')

-1

ви можете помістити імена полів у масив "поля", а потім:


sql = 'select %s from table where condition1=1 and condition2=2' % (
 ', '.join(fields))

якщо ваш список умов зросте, ви можете зробити те ж саме, використовуючи 'та' .join (умови)
jcomeau_ictx

з вашим рішенням запит було б ще складніше відредагувати, ніж з Option_4, а також його було б важко прочитати.
ssoler

@ssoler, це залежить від того, як хтось робить щось. Я оголошую кілька змінних у своїх програмах і замість цього використовую масиви рядків, що робить такі методи, як описані вище, дуже корисними та ремонтопридатними, принаймні мною.
jcomeau_ictx

-1

Я б запропонував дотримуватися варіанту 2 (я завжди використовую його для запитів, які є більш складними, ніж SELECT * FROM table), і якщо ви хочете надрукувати його в хороший спосіб, ви завжди можете використовувати окремий модуль .


-1

Для коротких запитів, які можуть вміститися в одному або двох рядках, я використовую рішення рядкового літералу у верхньому голосуванні вище. Для довших запитів я розбиваю їх на .sqlфайли. Потім я використовую функцію обгортки, щоб завантажити файл і виконати сценарій, приблизно так:

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)

Звичайно, це часто живе всередині класу, тому мені зазвичай не потрібно здавати cursorявно. Я також зазвичай використовую codecs.open(), але це набуває загальної ідеї. Тоді сценарії SQL повністю містяться у власних файлах із виділенням власного синтаксису.


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

[редагувати у відповіді до коментаря]
Наявність рядка SQL всередині методу НЕ означає, що вам доведеться його "складати":

>>> class Foo:
...     def fubar(self):
...         sql = """\
... select *
... from frobozz
... where zorkmids > 10
... ;"""
...         print sql
...
>>> Foo().fubar()
select *
from frobozz
where zorkmids > 10
;
>>>

ІМО це те саме, що Option_2
ssoler

@ssoler: Ваш параметр_2 має пробіли у всіх рядках; зауважте, що у вашому прикладі раніше пропущені пробіли select. У моїй відповіді немає пробілів. Що привело вас до формування думки, що вони однакові?
Джон Махін,

Якщо ви помістите рядок sql всередину методу, вам доведеться прокласти всі рядки (Option_2). Одним із можливих рішень цього є Option_3.
ssoler

@ssoler: Вибачте, я не розумію цього зауваження. Будь ласка, подивіться на мою оновлену відповідь.
Джон Макін

Ваша оновлена ​​відповідь - мій варіант_3, чи не так? Мені не подобається цей параметр, оскільки він порушує чіткість добре табличного коду.
ssoler
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.