Định dạng chuỗi truy vấn SQL trong Python


93

Tôi đang cố gắng tìm cách tốt nhất để định dạng chuỗi truy vấn sql. Khi tôi gỡ lỗi ứng dụng của mình, tôi muốn đăng nhập vào tệp tất cả các chuỗi truy vấn sql và điều quan trọng là chuỗi phải được định dạng đúng.

lựa chọn 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • Điều này tốt cho việc in chuỗi sql.
  • Nó không phải là một giải pháp tốt nếu chuỗi dài và không phù hợp với chiều rộng tiêu chuẩn là 80 ký tự.

Lựa chọn 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Ở đây mã rõ ràng nhưng khi bạn in chuỗi truy vấn sql, bạn nhận được tất cả những khoảng trắng khó chịu này.

    u '\ nselect field1, field2, field3, field4 \ n_ _ ___ from table \ n _ ___ trong đó condition1 = 1 \ n _ ___ _ và condition2 = 2'

Lưu ý: Tôi đã thay thế khoảng trắng bằng dấu gạch dưới _vì chúng được trình chỉnh sửa cắt bớt

Lựa chọn 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Tôi không thích tùy chọn này vì nó phá vỡ tính rõ ràng của mã được lập bảng tốt.

Lựa chọn 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • Tôi không thích tùy chọn này vì tất cả các lần nhập thêm vào mỗi dòng và cũng khó chỉnh sửa truy vấn.

Đối với tôi, giải pháp tốt nhất sẽ là Tùy chọn 2 nhưng tôi không thích các khoảng trắng thừa khi tôi in chuỗi sql.

Bạn có biết bất kỳ lựa chọn nào khác không?


Đây là những gì người Psycopg gọi là một cách tiếp cận ngây thơ đối với thành phần của chuỗi truy vấn, ví dụ như sử dụng nối chuỗi - initd.org/psycopg/docs/… . Thay vào đó, hãy sử dụng các tham số truy vấn để tránh các cuộc tấn công SQL injection và tự động chuyển đổi các đối tượng Python sang và từ các ký tự SQL. stackoverflow.com/questions/3134691/…
Matthew Cornell

Câu hỏi này thực sự không dành riêng cho các truy vấn SQL, nhưng áp dụng chung cho việc định dạng chuỗi nhiều dòng trong Python. Thẻ SQL nên được xóa.
cstork

Câu trả lời:


130

Xin lỗi vì đã đăng lên một chủ đề cũ như vậy - nhưng với tư cách là một người cũng có chung niềm đam mê với pythonic 'best', tôi nghĩ tôi sẽ chia sẻ giải pháp của chúng tôi.

Giải pháp là tạo các câu lệnh SQL bằng cách sử dụng String Literal Concatenation ( http://docs.python.org/ ) của python , có thể đủ điều kiện ở đâu đó giữa Tùy chọn 2 và Tùy chọn 4

Mẫu mã:

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

Hoạt động tốt với f-string :

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

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

Ưu điểm:

  1. Nó vẫn giữ định dạng 'được lập bảng tốt' của pythonic, nhưng không thêm các ký tự khoảng trắng không liên quan (gây ô nhiễm cho việc ghi nhật ký).
  2. Nó tránh được dấu gạch chéo ngược liên tục xấu xí của Lựa chọn 4, điều này gây khó khăn cho việc thêm các câu lệnh (chưa kể đến mù khoảng trắng).
  3. Và xa hơn, thực sự đơn giản để mở rộng câu lệnh trong VIM (chỉ cần đặt con trỏ vào điểm chèn và nhấn SHIFT-O để mở một dòng mới).

1
Nếu đây là để in, tôi nghĩ rằng một sự thay thế tốt hơn là viết nó như là chuỗi mutiline với """và sử dụng textwrap.dedent()trước khi xuất
slezica

Tôi đã chơi với tùy chọn đó, nhưng nó cũng làm cho đầu ra nhật ký có nhiều dòng. Khi theo dõi một ứng dụng chatty db, điều này gây ra đầu ra rất lớn.
user590028

1
Đây là một chủ đề cũ, nhưng tôi đã sử dụng định dạng này như là một thực hành tốt nhất, tuy nhiên nó được tẻ nhạt với truy vấn dài hơn
Jabda

7
Không phải lúc nào chúng ta cũng nên sử dụng dấu ngoặc kép "sql query"để tránh làm rối các chuỗi SQL (sử dụng dấu nháy đơn làm tiêu chuẩn)?
tpvasconcelos

19

Rõ ràng là bạn đã xem xét rất nhiều cách để viết SQL sao cho nó in ra được, nhưng làm thế nào về việc thay đổi câu lệnh 'print' mà bạn sử dụng để ghi nhật ký gỡ lỗi, thay vì viết SQL của bạn theo những cách bạn không thích? Sử dụng tùy chọn yêu thích của bạn ở trên, còn chức năng ghi nhật ký như thế này thì sao:

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)

Điều này cũng sẽ khiến việc thêm logic bổ sung để tách chuỗi đã ghi thành nhiều dòng trở nên đơn giản hơn nếu dòng dài hơn độ dài mong muốn của bạn.


11

Cách sạch sẽ nhất mà tôi đã gặp được lấy cảm hứng từ hướng dẫn kiểu sql .

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

Về cơ bản, các từ khóa bắt đầu một mệnh đề phải được căn phải và tên trường, v.v., phải được căn trái. Điều này trông rất gọn gàng và cũng dễ gỡ lỗi hơn.


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'

nếu giá trị của điều kiện phải là một chuỗi, bạn có thể làm như sau:

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
Xin đừng bao giờ làm điều này. Nó được gọi là SQL injection và nó thực sự nguy hiểm. Khá nhiều thư viện cơ sở dữ liệu Python cung cấp một cơ sở để sử dụng các tham số. Nếu bạn bắt gặp mình sử dụng format()các chuỗi SQL, đó là một mùi mã chính.
mattmc3

Tôi không nghĩ rằng chúng ta không thể sử dụng nó, bạn phải xác thực các thông số trước khi sử dụng nó, và bạn nên biết những gì bạn vượt qua.
pangpang

Việc xác thực dễ xảy ra lỗi hơn nhiều so với việc chỉ sử dụng where condition1=:field1và sau đó chuyển các giá trị dưới dạng tham số. Nếu bạn đang sử dụng .format(), sẽ có một cách để đưa một ';DROP TABLE Usersvào SQL của bạn. Hãy xem PEP-249 để biết cách sử dụng các thông số một cách chính xác. python.org/dev/peps/pep-0249/#paramstyle
mattmc3

0

Để tránh hoàn toàn việc định dạng , tôi nghĩ một giải pháp tuyệt vời là sử dụng các thủ tục .

Việc gọi một thủ tục cung cấp cho bạn kết quả của bất kỳ truy vấn nào bạn muốn đưa vào thủ tục này. Bạn thực sự có thể xử lý nhiều truy vấn trong một quy trình. Cuộc gọi sẽ chỉ trả về truy vấn cuối cùng đã được gọi.

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

bạn có thể đặt tên trường vào một mảng "trường", sau đó:


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

nếu danh sách các điều kiện của bạn phát triển, bạn có thể làm như vậy, sử dụng 'và' .join (điều kiện)
jcomeau_ictx

với giải pháp của bạn, truy vấn thậm chí sẽ khó chỉnh sửa hơn so với Option_4 và cũng sẽ khó đọc.
ssoler

@ssoler, điều đó phụ thuộc vào cách một người thực hiện mọi thứ. Tôi khai báo một số biến trong chương trình của mình và thay vào đó sử dụng mảng chuỗi, điều này làm cho các phương thức như trên rất hữu ích và có thể bảo trì được, ít nhất là đối với tôi.
jcomeau_ictx

-1

Tôi khuyên bạn nên sử dụng tùy chọn 2 (tôi luôn sử dụng nó cho các truy vấn phức tạp hơn SELECT * FROM table) và nếu bạn muốn in nó theo cách đẹp mắt, bạn có thể luôn sử dụng một mô-đun riêng biệt .


-1

Đối với các truy vấn ngắn có thể vừa trên một hoặc hai dòng, tôi sử dụng giải pháp ký tự chuỗi trong giải pháp được bình chọn nhiều nhất ở trên. Đối với các truy vấn dài hơn, tôi chia nhỏ chúng thành .sqlcác tệp. Sau đó, tôi sử dụng một hàm wrapper để tải tệp và thực thi tập lệnh, giống như:

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)

Tất nhiên điều này thường nằm bên trong một lớp nên tôi thường không phải vượt qua cursormột cách rõ ràng. Tôi cũng thường sử dụng codecs.open(), nhưng điều này có ý tưởng chung. Sau đó, các tập lệnh SQL hoàn toàn tự chứa trong các tệp của riêng chúng với tô sáng cú pháp của riêng chúng.


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

[sửa trong lần trả lời để nhận xét]
Có một chuỗi SQL bên trong một phương thức KHÔNG có nghĩa là bạn phải "lập bảng" cho nó:

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

IMO điều này giống với Option_2
ssoler

@ssoler: Option_2 của bạn có khoảng trắng ở đầu trên tất cả các dòng; lưu ý rằng ví dụ của bạn bỏ qua các khoảng trắng ở đầu trước đó select. Câu trả lời của tôi không có khoảng trắng ở đầu. Điều gì khiến bạn hình thành ý kiến ​​rằng họ giống nhau?
John Machin

Nếu bạn đặt chuỗi sql của mình bên trong một phương thức, bạn sẽ phải lập bảng tất cả các dòng (Option_2). Một giải pháp khả thi cho điều này là Option_3.
ssoler

@ssoler: Xin lỗi, tôi không hiểu nhận xét đó. Vui lòng xem câu trả lời cập nhật của tôi.
John Machin

Câu trả lời cập nhật của bạn là Option_3 của tôi, phải không? Tôi không thích tùy chọn này vì nó phá vỡ tính rõ ràng của mã được lập bảng tốt.
ssoler
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.