การจัดรูปแบบสตริงการสืบค้น Python SQL


102

ฉันกำลังพยายามหาวิธีที่ดีที่สุดในการจัดรูปแบบสตริงการสืบค้น 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 คุณจะได้ช่องว่างสีขาวที่น่ารำคาญเหล่านี้

    คุณ '\ 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 โทร AA วิธีไร้เดียงสาองค์ประกอบของสตริงแบบสอบถามเช่นใช้สตริง - initd.org/psycopg/docs/... ใช้พารามิเตอร์แบบสอบถามแทนเพื่อหลีกเลี่ยงการโจมตีด้วยการฉีด SQL และเพื่อแปลงวัตถุ Python เป็นและจากตัวอักษร SQL โดยอัตโนมัติ stackoverflow.com/questions/3134691/…
Matthew Cornell

คำถามนี้ไม่เฉพาะเจาะจงสำหรับการสืบค้น SQL แต่โดยทั่วไปจะใช้กับการจัดรูปแบบสตริงหลายบรรทัดใน Python ควรลบแท็ก SQL
cstork

คำตอบ:


136

ขออภัยที่โพสต์ในกระทู้เก่า - แต่ในฐานะคนที่มีความหลงใหลใน 'ดีที่สุด' ของ pythonic ฉันคิดว่าจะแบ่งปันวิธีแก้ปัญหา

วิธีแก้ปัญหาคือการสร้างคำสั่ง SQL โดยใช้ String Literal Concatenation ของ python ( http://docs.python.org/ ) ซึ่งอาจมีคุณสมบัติระหว่างตัวเลือก 2 และตัวเลือก 4

ตัวอย่างโค้ด:

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

ใช้งานได้ดีกับf-strings :

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

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

ข้อดี:

  1. โดยยังคงรูปแบบ 'well tabulated' ของ pythonic ไว้ แต่ไม่เพิ่มอักขระเว้นวรรค (ซึ่งก่อให้เกิดมลพิษในการบันทึก)
  2. หลีกเลี่ยงความอัปลักษณ์ต่อเนื่องของแบ็กสแลชของตัวเลือกที่ 4 ซึ่งทำให้ยากต่อการเพิ่มข้อความ (ไม่ต้องพูดถึงการตาบอดในพื้นที่สีขาว)
  3. และยิ่งไปกว่านั้นการขยายคำสั่งใน VIM นั้นง่ายมาก (เพียงแค่วางเคอร์เซอร์ไปที่จุดแทรกแล้วกดSHIFT-Oเพื่อเปิดบรรทัดใหม่)

2
ถ้านี่คือการพิมพ์ฉันคิดว่าทางเลือกที่ดีกว่าคือเขียนเป็นสตริง mutiline """และใช้textwrap.dedent()ก่อนที่จะส่งออก
slezica

ฉันเล่นกับตัวเลือกนั้น แต่มันก็สร้างไฟล์บันทึกหลายบรรทัดเช่นกัน เมื่อติดตามแอป db chatty สิ่งนี้ทำให้เกิดเอาต์พุตจำนวนมาก
user590028

1
นี่เป็นกระทู้เก่า แต่ฉันใช้รูปแบบนี้เป็นแนวทางปฏิบัติที่ดีที่สุด แต่มันน่าเบื่อกับการค้นหาที่ยาวขึ้น
Jabda

8
เราไม่ควรใช้เครื่องหมายคำพูดคู่เสมอ"sql query"เพื่อหลีกเลี่ยงการยุ่งกับสตริง SQL (ซึ่งใช้เครื่องหมายคำพูดเดี่ยวเป็นมาตรฐาน) หรือไม่?
tpvasconcelos

19

เห็นได้ชัดว่าคุณได้พิจารณาหลายวิธีในการเขียน SQL เพื่อให้สามารถพิมพ์ออกมาได้ แต่จะเปลี่ยนคำสั่ง 'พิมพ์' ที่คุณใช้สำหรับการบันทึกการดีบักได้อย่างไรแทนที่จะเขียน 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 injection และอันตรายจริงๆ ไลบรารีฐานข้อมูล Python ทุกตัวมีสิ่งอำนวยความสะดวกสำหรับการใช้พารามิเตอร์ หากคุณจับได้ว่าตัวเองใช้format()กับสตริง SQL มันเป็นกลิ่นรหัสที่สำคัญ
mattmc3

ฉันไม่คิดว่าเราจะใช้ไม่ได้คุณต้องตรวจสอบพารามิเตอร์ก่อนใช้งานและคุณควรรู้ว่าคุณผ่านอะไรมา
pangpang

การตรวจสอบความถูกต้องมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่าการใช้เพียงอย่างเดียวwhere condition1=:field1แล้วส่งผ่านค่าเป็นพารามิเตอร์ หากคุณกำลังใช้.format()งานจะมีวิธีเพิ่ม';DROP TABLE Usersเข้าใน SQL ของคุณ ดู PEP-249 สำหรับวิธีการใช้พารามิเตอร์อย่างถูกต้อง python.org/dev/peps/pep-0249/#paramstyle
mattmc3

1

คุณสามารถใช้inspect.cleandocเพื่อจัดรูปแบบคำสั่ง SQL ที่พิมพ์ได้อย่างสวยงาม

นี้ทำงานได้เป็นอย่างดีกับทางเลือกที่ 2

หมายเหตุ: print("-"*40)เป็นเพียงการแสดงให้เห็นถึงเส้นว่างที่ไม่จำเป็นเท่านั้นหากคุณไม่ได้ใช้ 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()

เอาท์พุต:

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

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

จากเอกสาร :

ตรวจสอบ .cleandoc (doc)

ล้างการเยื้องจาก docstrings ที่เยื้องเพื่อให้สอดคล้องกับบล็อกของโค้ด

ช่องว่างนำหน้าทั้งหมดจะถูกลบออกจากบรรทัดแรก ช่องว่างนำหน้าใด ๆ ที่สามารถลบได้อย่างสม่ำเสมอตั้งแต่บรรทัดที่สองเป็นต้นไปจะถูกลบออก บรรทัดว่างที่จุดเริ่มต้นและจุดสิ้นสุดจะถูกลบออกในภายหลัง นอกจากนี้แท็บทั้งหมดยังขยายเป็นช่องว่าง


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

คุณสามารถใส่ชื่อฟิลด์ลงในอาร์เรย์ "fields" จากนั้น:


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ไฟล์ จากนั้นฉันใช้ฟังก์ชัน wrapper เพื่อโหลดไฟล์และเรียกใช้สคริปต์บางอย่างเช่น:

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
;
>>>

IMO นี้เหมือนกับ Option_2
ssoler

@ssoler: Option_2 ของคุณมีช่องว่างนำหน้าทุกบรรทัด selectทราบว่าตัวอย่างของคุณละเว้นช่องว่างนำก่อน คำตอบของฉันไม่มีช่องว่างนำหน้า อะไรทำให้คุณมีความเห็นว่าพวกเขาเหมือนกัน?
John Machin

หากคุณใส่สตริง sql ของคุณในเมธอดคุณจะต้องจัดตารางบรรทัดทั้งหมด (Option_2) วิธีแก้ปัญหาที่เป็นไปได้วิธีหนึ่งคือ Option_3
ssoler

@ssoler: ขออภัยฉันไม่เข้าใจคำพูดนั้น โปรดดูคำตอบที่อัปเดตของฉัน
John Machin

คำตอบที่อัปเดตของคุณคือ Option_3 ของฉันใช่หรือไม่ ฉันไม่ชอบตัวเลือกนี้เพราะมันทำลายความชัดเจนของโค้ดที่เป็นตารางอย่างดี
ssoler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.