psycopg2: แทรกหลายแถวด้วยหนึ่งแบบสอบถาม


141

ฉันต้องการแทรกหลายแถวด้วยหนึ่งแบบสอบถาม (จำนวนแถวไม่คงที่) ดังนั้นฉันต้องดำเนินการแบบสอบถามเช่นนี้:

INSERT INTO t (a, b) VALUES (1, 2), (3, 4), (5, 6);

วิธีเดียวที่ฉันรู้คือ

args = [(1,2), (3,4), (5,6)]
args_str = ','.join(cursor.mogrify("%s", (x, )) for x in args)
cursor.execute("INSERT INTO t (a, b) VALUES "+args_str)

แต่ฉันต้องการวิธีที่ง่ายกว่า

คำตอบ:


219

ฉันสร้างโปรแกรมที่แทรกหลายบรรทัดไปยังเซิร์ฟเวอร์ที่อยู่ในเมืองอื่น

ผมพบว่าการใช้วิธีนี้ก็ประมาณ 10 executemanyครั้งได้เร็วกว่า ในกรณีของฉันtupเป็น tuple ที่มีประมาณ 2,000 แถว ใช้เวลาประมาณ 10 วินาทีเมื่อใช้วิธีนี้:

args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup)
cur.execute("INSERT INTO table VALUES " + args_str) 

และ 2 นาทีเมื่อใช้วิธีนี้:

cur.executemany("INSERT INTO table VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s)", tup)

15
ยังคงมีความเกี่ยวข้องมากเกือบสองปีต่อมา ประสบการณ์ในวันนี้แสดงให้เห็นว่าเมื่อจำนวนแถวที่คุณต้องการเพิ่มมากขึ้นจะยิ่งใช้executeกลยุทธ์ได้ดีขึ้น ฉันเห็นความเร็วประมาณ 100x ด้วยสิ่งนี้!
Rob Watts

4
อาจexecutemanyเรียกใช้คอมมิชชันหลังจากแทรกแต่ละครั้ง ถ้าคุณห่อทุกอย่างไว้ในธุรกรรมบางทีนั่นอาจจะทำให้สิ่งต่างๆ
ริชาร์ด

4
เพิ่งยืนยันการปรับปรุงนี้ด้วยตนเอง จากสิ่งที่ฉันได้อ่าน psycopg2 นั้นexecutemanyไม่ได้ทำสิ่งที่ดีที่สุดเพียงแค่ลูปและทำexecuteงบมากมาย เมื่อใช้วิธีนี้จะมีการแทรกแถว 700 ไปยังเซิร์ฟเวอร์ระยะไกลจาก 60 วินาทีเป็น <2 วินาที
เนลสัน

5
บางทีฉันอาจเป็นคนหวาดระแวง แต่การเชื่อมโยงข้อความค้นหาเข้าด้วยกัน+ดูเหมือนจะเปิดได้มากถึงการฉีด sql ฉันรู้สึกว่า @Clodoaldo Neto execute_values()solution นั้นปลอดภัยกว่า
Will Munn

26
ในกรณีที่มีคนพบข้อผิดพลาดต่อไปนี้: [TypeError: ลำดับรายการ 0: อินสแตนซ์ str ที่คาดไว้, พบไบต์] เรียกใช้คำสั่งนี้แทน [args_str = ','. join (cur.mogrify ("(% s,% s)", x ) .decode ("utf-8") สำหรับ x in tup)]
mrt

147

execute_valuesวิธีการใหม่ใน Psycopg 2.7:

data = [(1,'x'), (2,'y')]
insert_query = 'insert into t (a, b) values %s'
psycopg2.extras.execute_values (
    cursor, insert_query, data, template=None, page_size=100
)

วิธี pythonic ของการทำมันใน Psycopg 2.6:

data = [(1,'x'), (2,'y')]
records_list_template = ','.join(['%s'] * len(data))
insert_query = 'insert into t (a, b) values {}'.format(records_list_template)
cursor.execute(insert_query, data)

คำอธิบาย: หากได้รับข้อมูลที่จะถูกแทรกเป็นรายการของสิ่งอันดับเช่น

data = [(1,'x'), (2,'y')]

มันมีอยู่แล้วในรูปแบบที่ต้องการแน่นอนเป็น

  1. valuesไวยากรณ์ของinsertประโยคคาดว่ารายการของระเบียนในขณะที่

    insert into t (a, b) values (1, 'x'),(2, 'y')

  2. Psycopgปรับหลามtupleกับ recordPostgreSQL

งานที่จำเป็นเท่านั้นคือการจัดทำเทมเพลตรายการบันทึกที่จะเติมโดย psycopg

# We use the data list to be sure of the template length
records_list_template = ','.join(['%s'] * len(data))

และวางไว้ในinsertแบบสอบถาม

insert_query = 'insert into t (a, b) values {}'.format(records_list_template)

การพิมพ์insert_queryผลลัพธ์

insert into t (a, b) values %s,%s

ตอนนี้การPsycopgทดแทนข้อโต้แย้งปกติ

cursor.execute(insert_query, data)

หรือเพียงแค่ทดสอบสิ่งที่จะถูกส่งไปยังเซิร์ฟเวอร์

print (cursor.mogrify(insert_query, data).decode('utf8'))

เอาท์พุท:

insert into t (a, b) values (1, 'x'),(2, 'y')

1
ประสิทธิภาพของวิธีนี้เปรียบเทียบกับ cur.copy_from อย่างไร
Michael Goldshteyn

1
และนี่ก็เป็นส่วนสำคัญที่มีมาตรฐาน copy_from ปรับขนาดเป็น 6.5X เร็วขึ้นในเครื่องของฉันด้วยบันทึก 10M
โจเซฟ Sheedy

ดูดี - ฉันคิดว่าคุณมีเร่ร่อนในตอนท้ายของนิยามเริ่มต้นของ insert_query (ยกเว้นว่าคุณพยายามทำให้มันเป็น tuple?) และหายไปหลังจาก%% สำหรับ% s ในนิยามเริ่มต้นของ insert_query
deadcode

2
การใช้execute_valuesฉันสามารถทำให้ระบบของฉันทำงานที่ 1k บันทึกต่อนาทีได้มากถึง 128k บันทึกต่อนาที
4322 Conrad.Dean

66

อัพเดทด้วย psycopg2 2.7:

คลาสสิกexecutemany()ช้ากว่าการนำไปใช้ของ @ ant32 ประมาณ 60 เท่า (เรียกว่า "folded") ดังที่อธิบายไว้ในหัวข้อนี้: https://www.postgresql.org/message-id/20170130215151.GA7081%40deb76.aryehleib.com

การใช้งานนี้ถูกเพิ่มไปยัง psycopg2 ในรุ่น 2.7 และเรียกว่าexecute_values():

from psycopg2.extras import execute_values
execute_values(cur,
    "INSERT INTO test (id, v1, v2) VALUES %s",
    [(1, 2, 3), (4, 5, 6), (7, 8, 9)])

คำตอบก่อนหน้า:

เพื่อแทรกหลายแถวโดยใช้ multirow VALUESไวยากรณ์ด้วยexecute()เป็นเรื่องเกี่ยวกับ 10 เท่าเร็วกว่าการใช้ executemany()psycopg2 อันที่จริงexecutemany()เพียงแค่เรียกใช้INSERTงบบุคคลจำนวนมาก

รหัสของ @ ant32 ทำงานได้อย่างสมบูรณ์แบบใน Python 2 แต่ใน Python 3 ให้cursor.mogrify()ผลตอบแทนไบต์cursor.execute()ใช้ทั้งไบต์หรือสตริงและ','.join()คาดว่าstrอินสแตนซ์

ดังนั้นใน Python 3 คุณอาจต้องแก้ไขรหัสของ @ ant32 โดยการเพิ่ม.decode('utf-8'):

args_str = ','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x).decode('utf-8') for x in tup)
cur.execute("INSERT INTO table VALUES " + args_str)

หรือโดยใช้ไบต์ (มีb''หรือb"") เท่านั้น:

args_bytes = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup)
cur.execute(b"INSERT INTO table VALUES " + args_bytes) 

26

cursor.copy_fromเป็นวิธีแก้ปัญหาที่เร็วที่สุดที่ฉันเคยพบสำหรับเม็ดมีดจำนวนมาก นี่คือส่วนสำคัญที่ฉันสร้างขึ้นพร้อมกับคลาสที่ชื่อว่า IteratorFile ซึ่งอนุญาตให้ตัววนซ้ำที่ยอมให้สตริงอ่านเหมือนไฟล์ เราสามารถแปลงแต่ละบันทึกอินพุตเป็นสตริงโดยใช้นิพจน์ตัวสร้าง ดังนั้นทางออกก็คือ

args = [(1,2), (3,4), (5,6)]
f = IteratorFile(("{}\t{}".format(x[0], x[1]) for x in args))
cursor.copy_from(f, 'table_name', columns=('a', 'b'))

สำหรับขนาดเล็ก ๆ ของ args นี้มันไม่ได้สร้างความแตกต่างด้านความเร็วได้มากนัก แต่ฉันเห็น speedups ที่ยิ่งใหญ่เมื่อต้องรับมือกับหลายพันแถว นอกจากนี้ยังจะมีหน่วยความจำที่มีประสิทธิภาพมากกว่าการสร้างสตริงการสืบค้นขนาดยักษ์ ตัววนซ้ำจะบันทึกระเบียนอินพุตหนึ่งครั้งในหน่วยความจำในแต่ละครั้งซึ่งในบางจุดคุณจะมีหน่วยความจำไม่เพียงพอในกระบวนการ Python ของคุณหรือใน Postgres โดยการสร้างสตริงข้อความค้นหา


3
นี่คือมาตรฐานเปรียบเทียบ copy_from / IteratorFile กับโซลูชันตัวสร้างแบบสอบถาม copy_from ปรับขนาดเป็น 6.5X เร็วขึ้นในเครื่องของฉันด้วยบันทึก 10M
โจเซฟ Sheedy

3
คุณต้องไปไหนมาไหนกับการหลบหนีสตริงและเวลาประทับ ฯลฯ ?
CpILL

ใช่คุณต้องตรวจสอบให้แน่ใจว่าคุณมีบันทึก TSV ที่มีรูปแบบที่ดี
โจเซฟ Sheedy

24

ตัวอย่างจากหน้าการสอนของ Psycopg2 ที่Postgresql.org (ดูด้านล่าง) :

รายการสุดท้ายที่ฉันต้องการแสดงให้คุณเห็นคือวิธีแทรกหลายแถวโดยใช้พจนานุกรม หากคุณมีสิ่งต่อไปนี้:

namedict = ({"first_name":"Joshua", "last_name":"Drake"},
            {"first_name":"Steven", "last_name":"Foo"},
            {"first_name":"David", "last_name":"Bar"})

คุณสามารถแทรกทั้งสามแถวในพจนานุกรมได้อย่างง่ายดายโดยใช้:

cur = conn.cursor()
cur.executemany("""INSERT INTO bar(first_name,last_name) VALUES (%(first_name)s, %(last_name)s)""", namedict)

มันไม่ได้บันทึกรหัสมากนัก แต่มันก็ดูดีกว่าอย่างแน่นอน


35
สิ่งนี้จะเรียกใช้INSERTคำสั่งหลายรายการ มีประโยชน์ แต่ไม่เหมือนกันกับการVALUEแทรกแบบหลายเดียว
Craig Ringer

7

เทคนิคเหล่านี้ทั้งหมดเรียกว่า 'Extended Inserts' ในคำศัพท์ของ Postgres และ ณ วันที่ 24 พฤศจิกายน 2559 มันยังเร็วกว่าการดำเนินการของ psychopg2 () และวิธีอื่น ๆ ทั้งหมดที่ระบุไว้ในชุดข้อความนี้ (ซึ่งฉันพยายามมาก่อน ตอบ).

นี่คือรหัสบางส่วนที่ไม่ได้ใช้ cur.mogrify และเป็นสิ่งที่ดีและง่ายต่อการเข้าใจ

valueSQL = [ '%s', '%s', '%s', ... ] # as many as you have columns.
sqlrows = []
rowsPerInsert = 3 # more means faster, but with diminishing returns..
for row in getSomeData:
        # row == [1, 'a', 'yolo', ... ]
        sqlrows += row
        if ( len(sqlrows)/len(valueSQL) ) % rowsPerInsert == 0:
                # sqlrows == [ 1, 'a', 'yolo', 2, 'b', 'swag', 3, 'c', 'selfie' ]
                insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*rowsPerInsert)
                cur.execute(insertSQL, sqlrows)
                con.commit()
                sqlrows = []
insertSQL = 'INSERT INTO "twitter" VALUES ' + ','.join(['(' + ','.join(valueSQL) + ')']*len(sqlrows))
cur.execute(insertSQL, sqlrows)
con.commit()

แต่ควรสังเกตว่าถ้าคุณสามารถใช้ copy_from () คุณควรใช้ copy_from;)


นำขึ้นมาจากความตาย แต่เกิดอะไรขึ้นในสถานการณ์ของสองสามแถวสุดท้าย? ฉันสมมติว่าคุณใช้ประโยคสุดท้ายนั้นอีกครั้งในแถวสุดท้ายที่เหลือในกรณีที่คุณมีจำนวนแถวเท่ากัน?
mcpeterson

ถูกต้องขอโทษฉันต้องลืมที่จะทำเช่นนั้นเมื่อฉันเขียนตัวอย่าง - นั่นคือความโง่ของฉัน ไม่ทำเช่นนั้นจะไม่ทำให้เกิดข้อผิดพลาดกับผู้คนซึ่งทำให้ฉันเป็นห่วงว่ามีคนคัดลอก / วางวิธีแก้ปัญหาและดำเนินธุรกิจต่อไป ..... ขอบคุณ mcpeterson ขอบคุณมาก - ขอบคุณ!
JJ

2

ฉันใช้คำตอบของ ant32 มาหลายปีแล้ว อย่างไรก็ตามฉันพบว่านั่นเป็นข้อผิดพลาดใน python 3 เพราะmogrifyส่งคืนสตริงไบต์

การแปลงสตริง bytse อย่างชัดเจนเป็นโซลูชันที่ง่ายสำหรับการใช้งานโค้ด python 3

args_str = b','.join(cur.mogrify("(%s,%s,%s,%s,%s,%s,%s,%s,%s)", x) for x in tup) 
cur.execute(b"INSERT INTO table VALUES " + args_str)

1

อีกวิธีที่ดีและมีประสิทธิภาพ - คือการส่งผ่านแถวสำหรับการแทรกเป็นอาร์กิวเมนต์ 1 ซึ่งเป็นอาร์เรย์ของวัตถุ json

เช่นคุณผ่านการโต้แย้ง:

[ {id: 18, score: 1}, { id: 19, score: 5} ]

มันเป็นอาร์เรย์ซึ่งอาจมีจำนวนวัตถุใด ๆ ที่อยู่ภายใน จากนั้น SQL ของคุณจะมีลักษณะดังนี้:

INSERT INTO links (parent_id, child_id, score) 
SELECT 123, (r->>'id')::int, (r->>'score')::int 
FROM unnest($1::json[]) as r 

แจ้งให้ทราบล่วงหน้า: โพสต์ของคุณจะต้องใหม่พอที่จะสนับสนุน json


1

cursor.copyfromวิธีการแก้ปัญหาให้เป็นไปตาม@ jopseph.sheedy ( https://stackoverflow.com/users/958118/joseph-sheedy ) เหนือ ( https://stackoverflow.com/a/30721460/11100064 ) เป็นจริงเร็วฟ้าผ่า

อย่างไรก็ตามตัวอย่างที่เขาให้นั้นไม่สามารถใช้งานได้โดยทั่วไปสำหรับบันทึกที่มีจำนวนฟิลด์และฉันใช้เวลาในขณะที่คิดวิธีการใช้อย่างถูกต้อง

IteratorFile จำเป็นต้องมีอินสแตนซ์ของฟิลด์ที่คั่นด้วยแท็บเช่นนี้ ( rเป็นรายการของ dicts ที่แต่ละ dict เป็นเรคคอร์ด):

    f = IteratorFile("{0}\t{1}\t{2}\t{3}\t{4}".format(r["id"],
        r["type"],
        r["item"],
        r["month"],
        r["revenue"]) for r in records)

ในการพูดคุยทั่วไปสำหรับจำนวนฟิลด์โดยพลการครั้งแรกเราจะสร้างสตริงบรรทัดที่มีจำนวนแท็บและตัวยึดพื้นที่ที่ถูกต้อง: "{}\t{}\t{}....\t{}"จากนั้นใช้.format()เพื่อกรอกค่าฟิลด์ *list(r.values())) for r in records::

        line = "\t".join(["{}"] * len(records[0]))

        f = IteratorFile(line.format(*list(r.values())) for r in records)

ฟังก์ชั่นที่สมบูรณ์ในส่วนสำคัญที่นี่


0

หากคุณใช้ SQLAlchemy คุณไม่จำเป็นต้องยุ่งกับการสร้างสตริงด้วยมือเพราะ SQLAlchemy สนับสนุนการสร้างVALUESประโยคหลายแถวสำหรับINSERTคำสั่งเดียว :

rows = []
for i, name in enumerate(rawdata):
    row = {
        'id': i,
        'name': name,
        'valid': True,
    }
    rows.append(row)
if len(rows) > 0:  # INSERT fails if no rows
    insert_query = SQLAlchemyModelName.__table__.insert().values(rows)
    session.execute(insert_query)

ภายใต้ประทุน SQLAlchemy ใช้ executemany ของ Psychopg2 () สำหรับการโทรแบบนี้และดังนั้นคำตอบนี้จะมีปัญหาด้านประสิทธิภาพที่รุนแรงสำหรับการสืบค้นขนาดใหญ่ ดูรันวิธีdocs.sqlalchemy.org/en/latest/orm/session_api.html
sage88

2
ฉันไม่คิดว่าเป็นอย่างนั้น มันค่อนข้างน้อยตั้งแต่ที่ฉันดูที่นี่ แต่ IIRC นี่คือการสร้างคำสั่งแทรกเดียวในinsert_queryบรรทัด จากนั้นsession.execute()จะเรียกexecute()คำสั่งของ psycopg2 ด้วยสตริงขนาดใหญ่เพียงเส้นเดียว ดังนั้น "เคล็ดลับ" คือการสร้างวัตถุคำสั่งแทรกทั้งหมดก่อน ฉันใช้นี้เพื่อแทรก 200,000 executemany()แถวในเวลาและเพิ่มประสิทธิภาพการทำงานเห็นขนาดใหญ่ใช้รหัสนี้เมื่อเทียบกับปกติ
Jeff Widman

1
เอกสาร SQLAlchemy ที่คุณเชื่อมโยงมีส่วนที่แสดงให้เห็นว่ามันทำงานอย่างไรและแม้กระทั่งพูดว่า: "มันเป็นสิ่งสำคัญที่จะต้องทราบว่าการส่งผ่านค่าหลายค่าไม่เหมือนกับการใช้แบบฟอร์ม executemany () แบบดั้งเดิม" ดังนั้นจึงเรียกอย่างชัดเจนว่าใช้งานได้
Jeff Widman

1
ฉันยืนแก้ไขแล้ว ฉันไม่ได้สังเกตเห็นการใช้งานของค่า () วิธี (โดยไม่ต้องมัน SQLAlchemy เพียงแค่รันเอ็กซีคิวต์) ฉันจะพูดว่าแก้ไขคำตอบเพื่อรวมลิงค์ไปยังเอกสารนั้นเพื่อให้ฉันสามารถเปลี่ยนการลงคะแนนของฉัน แต่แน่นอนว่าคุณได้รวมไว้แล้ว อาจพูดถึงว่านี่ไม่ใช่สิ่งเดียวกับการเรียกใช้ insert () กับ execute () พร้อมรายการ dicts ใช่ไหม
sage88

มันทำงานอย่างไรเมื่อเทียบกับ execute_values
MrR

0

execute_batch ถูกเพิ่มใน psycopg2 ตั้งแต่คำถามนี้ถูกโพสต์

มันช้ากว่าexecute_valuesแต่ใช้ง่ายกว่า


2
ดูความคิดเห็นอื่น วิธีของ psycopg2 execute_valuesนั้นเร็วกว่าexecute_batch
Fierr

0

executemanyยอมรับอาร์เรย์ของ tuples

https://www.postgresqltutorial.com/postgresql-python/insert/

    """ array of tuples """
    vendor_list = [(value1,)]

    """ insert multiple vendors into the vendors table  """
    sql = "INSERT INTO vendors(vendor_name) VALUES(%s)"
    conn = None
    try:
        # read database configuration
        params = config()
        # connect to the PostgreSQL database
        conn = psycopg2.connect(**params)
        # create a new cursor
        cur = conn.cursor()
        # execute the INSERT statement
        cur.executemany(sql,vendor_list)
        # commit the changes to the database
        conn.commit()
        # close communication with the database
        cur.close()
    except (Exception, psycopg2.DatabaseError) as error:
        print(error)
    finally:
        if conn is not None:
            conn.close()

-1

หากคุณต้องการแทรกหลายแถวภายในหนึ่งแทรก statemens (สมมติว่าคุณไม่ได้ใช้ ORM) วิธีที่ง่ายที่สุดสำหรับฉันคือการใช้รายการพจนานุกรม นี่คือตัวอย่าง:

 t = [{'id':1, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 6},
      {'id':2, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 7},
      {'id':3, 'start_date': '2015-07-19 00:00:00', 'end_date': '2015-07-20 00:00:00', 'campaignid': 8}]

conn.execute("insert into campaign_dates
             (id, start_date, end_date, campaignid) 
              values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s);",
             t)

ตามที่คุณเห็นเพียงหนึ่งแบบสอบถามจะถูกดำเนินการ:

INFO sqlalchemy.engine.base.Engine insert into campaign_dates (id, start_date, end_date, campaignid) values (%(id)s, %(start_date)s, %(end_date)s, %(campaignid)s);
INFO sqlalchemy.engine.base.Engine [{'campaignid': 6, 'id': 1, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 7, 'id': 2, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}, {'campaignid': 8, 'id': 3, 'end_date': '2015-07-20 00:00:00', 'start_date': '2015-07-19 00:00:00'}]
INFO sqlalchemy.engine.base.Engine COMMIT

การแสดงการบันทึกจากเครื่องมือ sqlalchemy ไม่ใช่การสาธิตการเรียกใช้คิวรีเดียวมันหมายถึงว่าเครื่องยนต์ sqlalchemy รันหนึ่งคำสั่ง ภายใต้ประทุนนี่คือการใช้งานโปรแกรมประมวลผลของ psychopg2 ซึ่งไม่มีประสิทธิภาพมาก ดูรันวิธีdocs.sqlalchemy.org/en/latest/orm/session_api.html
sage88

-3

ใช้ aiopg - ตัวอย่างด้านล่างทำงานได้อย่างสมบูรณ์แบบ

    # items = [10, 11, 12, 13]
    # group = 1
    tup = [(gid, pid) for pid in items]
    args_str = ",".join([str(s) for s in tup])
    # insert into group values (1, 10), (1, 11), (1, 12), (1, 13)
    yield from cur.execute("INSERT INTO group VALUES " + args_str)


-4

ในที่สุดในรุ่น SQLalchemy1.2 การใช้งานใหม่นี้จะถูกเพิ่มเข้ามาเพื่อใช้ psycopg2.extras.execute_batch () แทนการประมวลผลเมื่อคุณเริ่มต้นโปรแกรมด้วย use_batch_mode = จริงเช่น:

engine = create_engine(
    "postgresql+psycopg2://scott:tiger@host/dbname",
    use_batch_mode=True)

http://docs.sqlalchemy.org/en/latest/changelog/migration_12.html#change-4109

จากนั้นบางคนจะต้องใช้ SQLalchmey ไม่ต้องกังวลที่จะลองชุดต่าง ๆ ของ sqla และ psycopg2 และสั่ง SQL โดยตรงเข้าด้วยกัน

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