การสร้างรายการสำหรับใช้ใน python MySQLDB IN clause


86

ฉันรู้วิธีแมปรายการกับสตริง:

foostring = ",".join( map(str, list_of_ids) )

และฉันรู้ว่าฉันสามารถใช้สิ่งต่อไปนี้เพื่อทำให้สตริงนั้นเป็นประโยค IN:

cursor.execute("DELETE FROM foo.bar WHERE baz IN ('%s')" % (foostring))

สิ่งที่ฉันต้องการคือทำสิ่งเดียวกันให้สำเร็จ (หลีกเลี่ยงการฉีด SQL) โดยใช้ MySQLDB ในตัวอย่างข้างต้นเนื่องจากไม่ได้ส่ง foostring เป็นอาร์กิวเมนต์เพื่อดำเนินการจึงมีช่องโหว่ ฉันยังต้องพูดและหนีออกนอกไลบรารี mysql

(มีคำถาม SO ที่เกี่ยวข้องแต่คำตอบที่แสดงในนั้นไม่สามารถใช้ได้กับ MySQLDB หรือเสี่ยงต่อการแทรก SQL)


คุณอาจได้รับแรงบันดาลใจจากคำถามคล้าย ๆ กันที่ทำใน php stackoverflow.com/questions/327274/…
Zoredache


@mluebke มีความคิดเกี่ยวกับการส่งหลายรายการในแบบสอบถามหรือไม่?
Dipen Dedania

คำตอบ:


161

ใช้list_of_idsโดยตรง:

format_strings = ','.join(['%s'] * len(list_of_ids))
cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings,
                tuple(list_of_ids))

ด้วยวิธีนี้คุณจะหลีกเลี่ยงการอ้างตัวเองและหลีกเลี่ยงการฉีด sql ทุกชนิด

โปรดทราบว่า data ( list_of_ids) จะไปที่ไดรเวอร์ของ mysql โดยตรงเป็นพารามิเตอร์ (ไม่อยู่ในข้อความค้นหา) ดังนั้นจึงไม่มีการแทรก คุณสามารถทิ้งตัวอักษรที่คุณต้องการไว้ในสตริงโดยไม่จำเป็นต้องลบหรืออ้างตัวอักษร


2
@heikogerlach: ฉันไม่ได้อ้างถึง% s ... บรรทัดแรกสร้างสตริง "% s,% s,% s" ... ขนาดของ list_of_ids length เท่ากัน
nosklo

อ๊ะคุณพูดถูก ต้องดูหนักขึ้น ฉันผสมมันขึ้นมา เป็นทางออกที่ดีแม้ว่า

สิ่งนี้จะทำงานใน sqlite ด้วยหรือไม่? เพราะฉันเพิ่งลองใช้และดูเหมือนว่าจะชี้ให้เห็นข้อผิดพลาดทางไวยากรณ์
Sohaib

@Sohaib ใน sqlite ถ่านทดแทน?ไม่%sได้ผลถ้าคุณเปลี่ยนบรรทัดแรกเป็นformat_strings = ','.join('?' * len(list_of_ids)).
nosklo

1
@kdas ในกรณีของคุณคุณไม่ต้องการให้% format_stringsส่วนอื่นเปลี่ยนส่วนอื่น%sยึดตำแหน่งในแบบสอบถามของคุณมีเพียงIN (%s)ตัวยึดเท่านั้น- วิธีที่จะบรรลุสิ่งนี้คือการเพิ่ม%ตัวอักษรทั้งหมดเป็นสองเท่ายกเว้นตัวที่คุณต้องการแทนที่:query = ("select distinct cln from vcf_commits where branch like %%s and repository like %%s and filename in (%s) and author not like %%s" % format_strings,); cursor.execute(query, (branch, repository) + tuple(fname_list) + (invalid_author,))
nosklo

6

แม้ว่าคำถามนี้จะค่อนข้างเก่า แต่คิดว่าจะดีกว่าถ้าตอบกลับไปเผื่อว่ามีคนอื่นกำลังมองหาสิ่งที่ฉันต้องการ

คำตอบที่ยอมรับจะยุ่งเหยิงเมื่อเรามีพารามิเตอร์จำนวนมากหรือหากเราต้องการใช้พารามิเตอร์ที่มีชื่อ

หลังจากการทดลองบางอย่าง

ids = [5, 3, ...]  # list of ids
cursor.execute('''
SELECT 
...
WHERE
  id IN %(ids)s
  AND created_at > %(start_dt)s
''', {
  'ids': tuple(ids), 'start_dt': '2019-10-31 00:00:00'
})

ทดสอบกับpython2.7,pymysql==0.7.11


4
สิ่งนี้ใช้ไม่ได้กับ python 3 และ mysql-connector-python 8.0.21 ข้อผิดพลาด "Python tuple ไม่สามารถแปลงเป็น MySQL type" ถูกส่งกลับ
Rubms

-2

หากคุณใช้Django 2.0 or 2.1และPython 3.6นี่คือวิธีที่ถูกต้อง:

from django.db import connection
RESULT_COLS = ['col1', 'col2', 'col3']
RESULT_COLS_STR = ', '.join(['a.'+'`'+i+'`' for i in RESULT_COLS])
QUERY_INDEX = RESULT_COLS[0]

TABLE_NAME = 'test'
search_value = ['ab', 'cd', 'ef']  # <-- a list
query = (
    f'SELECT DISTINCT {RESULT_COLS_STR} FROM {TABLE_NAME} a '
    f'WHERE a.`{RESULT_COLS[0]}` IN %s '
    f'ORDER BY a.`{RESULT_COLS[0]}`;'
)  # <- 'SELECT DISTINCT a.`col1`, a.`col2`, a.`col3` FROM test a WHERE a.`col1` IN %s ORDER BY a.`col1`;'
with connection.cursor() as cursor:
    cursor.execute(query, params=[search_value])  # params is a list with a list as its element

อ้างอิง: https://stackoverflow.com/a/23891759/2803344 https://docs.djangoproject.com/en/2.1/topics/db/sql/#passing-parameters-into-raw


หากมีอะไรผิดพลาดโปรดแก้ไขด้วย!
Belter

-2

แม้ว่าคำถามนี้จะค่อนข้างเก่า ฉันกำลังแบ่งปันวิธีแก้ปัญหาของฉันหากสามารถช่วยใครสักคนได้

list_to_check = ['A', 'B'] cursor.execute("DELETE FROM foo.bar WHERE baz IN ({})".format(str(list_to_check)[1:-1])

ทดสอบด้วย Python=3.6


1
ฉันกลัวว่าโซลูชันนี้จะเสี่ยงต่อการโจมตีด้วยการแทรก SQL เนื่องจากสิ่งที่ให้มาlist_to_checkนั้นไม่ได้เป็นค่า Escape SQL นี่คือเหตุผลที่การส่งผ่านค่าเป็นพารามิเตอร์ไปexecuteจึงเหมาะสมกว่า ใช้โซลูชันนี้อย่างระมัดระวัง (นั่นคือรหัสอินพุตจะไม่ได้รับเป็นพารามิเตอร์จากภายนอกแอปพลิเคชันของคุณ) เนื่องจากอาจมีคนใช้สิ่งนี้เพื่อโจมตีระบบของคุณและเข้าถึงฐานข้อมูลของคุณ
Rubms

-3

อีกวิธีง่ายๆโดยใช้การทำความเข้าใจรายการ:

# creating a new list of strings and convert to tuple
sql_list = tuple([ key.encode("UTF-8") for key in list_of_ids ])

# replace "{}" with "('id1','id2',...'idlast')"
cursor.execute("DELETE FROM foo.bar WHERE baz IN {}".format(sql_list))

-4
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

สิ่งนี้สามารถใช้ได้กับ use-case บางกรณีหากคุณไม่ต้องการที่จะเกี่ยวข้องกับวิธีการที่คุณต้องส่งผ่านอาร์กิวเมนต์เพื่อให้สตริงการสืบค้นสมบูรณ์และต้องการเรียกใช้เพียง cursror.execute(query)ซึ่งอาจทำงานบางกรณีการใช้งานถ้าคุณไม่ต้องการจะเกี่ยวข้องกับวิธีการที่คุณต้องผ่านการขัดแย้งที่จะเสร็จสมบูรณ์สตริงการสืบค้นและต้องการที่จะเรียกเพียง

อีกวิธีหนึ่งอาจเป็น:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)

-7

ง่ายมาก: เพียงใช้รูปแบบด้านล่าง

Rules_id = ["9", "10"]

sql1 = "เลือก * จากการเข้าร่วม _rules_staff รหัส WHERE ใน (" + "," .join (แผนที่ (str, rules_id)) + ")"

"," .join (แผนที่ (str, rules_id))


มันทำ sql quoting ที่ไหนและไม่ได้ใช้ลิเทอรัลแทนการผูกตัวแปร?
eckes

ไม่จำเป็นต้องใช้งานได้ดี คุณสามารถทดสอบได้เนื่องจากการสร้างทูเพิลแปลงโดยตรงเป็นสตริงโดยใช้วงเล็บปีกกาแรก ("9", "10") ซึ่งปรับรูปแบบ sql ดังนั้นคุณไม่จำเป็นต้องมีรูปแบบอื่นในการสร้างคือ sql adjastable
Mizanur Rahman

1
และถ้าrules_idมี"); DROP TABLES Bobby --?
eckes

บอกแล้วว่า "ระเบิดรายการ" ไม่ ") ... ดังนั้นก่อนสอบถามคุณต้องตรวจสอบความถูกต้อง
Mizanur Rahman

หรือใช้: sql1 = "SELECT * FROM joinance_rules_staff WHERE id in (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.