การแปลง Pandas DataFrame ให้เป็นคำสั่ง VALUES sql

เมื่อใช้ pandas ใน python ฉันจะต้องสามารถสร้างการสืบค้นที่มีประสิทธิภาพจาก dataframe ไปยัง postgresql ได้ น่าเสียดายที่ DataFrame.to_sql(...) ดำเนินการแทรกโดยตรงเท่านั้นและแบบสอบถามที่ฉันต้องการทำนั้นค่อนข้างซับซ้อน

ตามหลักการแล้ว ฉันต้องการทำสิ่งนี้:

WITH my_data AS (
  SELECT * FROM (
    VALUES 
    <dataframe data>
  ) AS data (col1, col2, col3)
)
UPDATE my_table 
SET
my_table.col1 = my_data.col1,
my_table.col2 = complex_function(my_table.col2, my_data.col2),
FROM my_data
WHERE my_table.col3 < my_data.col3;

อย่างไรก็ตาม เพื่อทำเช่นนั้น ฉันจะต้องเปลี่ยน dataframe ของฉันให้เป็นคำสั่งค่าธรรมดา แน่นอนว่าฉันสามารถเขียนฟังก์ชันของตัวเองใหม่ได้ แต่ประสบการณ์ที่ผ่านมาได้สอนฉันว่าการเขียนฟังก์ชันเพื่อหนีและฆ่าเชื้อ sql ไม่ควรทำด้วยตนเอง

เรากำลังใช้ SQLAlchemy แต่ดูเหมือนว่าพารามิเตอร์ที่ถูกผูกไว้จะใช้ได้กับอาร์กิวเมนต์จำนวนจำกัดเท่านั้น และตามหลักการแล้ว ฉันต้องการให้ดาต้าเฟรมเป็นข้อความเป็นอนุกรมที่ความเร็ว C

มีวิธีใดไม่ว่าจะผ่าน pandas หรือผ่าน SQLAlchemy ในการเปลี่ยน dataframe ของฉันอย่างมีประสิทธิภาพให้เป็นคำสั่งย่อยของค่าและแทรกลงในแบบสอบถามของฉันหรือไม่


person Rémi Bonnet    schedule 03.06.2019    source แหล่งที่มา
comment
ฉันมีอันที่คล้ายกันซึ่งฉันบันทึกเป็น proc และใช้แพนด้า i pd.read_sql_query('EXEC proc_name') แจ้งให้เราทราบหากฉันเข้าใจผิดคำถาม   -  person anky    schedule 03.06.2019


คำตอบ (1)


คุณสามารถใช้ psycopg2.extras.execute_values ได้ ตัวอย่างเช่น เมื่อได้รับการตั้งค่านี้

CREATE TABLE my_table (
col1 int
, col2 text
, col3 int
);
INSERT INTO my_table VALUES 
(99, 'X', 1)
, (99, 'Y', 2)
, (99, 'Z', 99);

# | col1 | col2 | col3 |
# |------+------+------|
# |   99 | X    |    1 |
# |   99 | Y    |    2 |
# |   99 | Z    |   99 |

รหัสหลาม

import psycopg2
import psycopg2.extras as pge
import pandas as pd
import config

df = pd.DataFrame([
    (1, 'A', 10), 
    (2, 'B', 20),
    (3, 'C', 30)])

with psycopg2.connect(host=config.HOST, user=config.USER, password=config.PASS, database=config.USER) as conn:
    with conn.cursor() as cursor:
        sql = '''WITH my_data AS (
          SELECT * FROM (
            VALUES %s
          ) AS data (col1, col2, col3)
        )
        UPDATE my_table 
        SET
        col1 = my_data.col1,
        -- col2 = complex_function(col2, my_data.col2)
        col2 = my_table.col2 || my_data.col2
        FROM my_data
        WHERE my_table.col3 < my_data.col3'''

        pge.execute_values(cursor, sql, df.values)

อัปเดต my_table ให้เป็น

# SELECT * FROM my_table
| col1 | col2 | col3 |
|------+------+------|
|   99 | Z    |   99 |
|    1 | XA   |    1 |
|    1 | YA   |    2 |

หรือคุณสามารถใช้ psycopg2 เพื่อสร้าง SQL รหัสใน format_values ถูกคัดลอกเกือบทั้งหมดจาก ซอร์สโค้ดสำหรับ pge.execute_values .

import psycopg2
import psycopg2.extras as pge
import pandas as pd
import config

df = pd.DataFrame([
    (1, "A'foo'", 10), 
    (2, 'B', 20),
    (3, 'C', 30)])


def format_values(cur, sql, argslist, template=None, page_size=100):
    enc = pge._ext.encodings[cur.connection.encoding]
    if not isinstance(sql, bytes):
        sql = sql.encode(enc)
    pre, post = pge._split_sql(sql)
    result = []
    for page in pge._paginate(argslist, page_size=page_size):
        if template is None:
            template = b'(' + b','.join([b'%s'] * len(page[0])) + b')'
        parts = pre[:]
        for args in page:
            parts.append(cur.mogrify(template, args))
            parts.append(b',')
        parts[-1:] = post
        result.append(b''.join(parts))
    return b''.join(result).decode(enc)

with psycopg2.connect(host=config.HOST, user=config.USER, password=config.PASS, database=config.USER) as conn:
    with conn.cursor() as cursor:
        sql = '''WITH my_data AS (
          SELECT * FROM (
            VALUES %s
          ) AS data (col1, col2, col3)
        )
        UPDATE my_table 
        SET
        col1 = my_data.col1,
        -- col2 = complex_function(col2, my_data.col2)
        col2 = my_table.col2 || my_data.col2
        FROM my_data
        WHERE my_table.col3 < my_data.col3'''

        print(format_values(cursor, sql, df.values))

อัตราผลตอบแทน

WITH my_data AS (
          SELECT * FROM (
            VALUES (1,'A''foo''',10),(2,'B',20),(3,'C',30)
          ) AS data (col1, col2, col3)
        )
        UPDATE my_table 
        SET
        col1 = my_data.col1,
        -- col2 = complex_function(col2, my_data.col2)
        col2 = my_table.col2 || my_data.col2
        FROM my_data
        WHERE my_table.col3 < my_data.col3
person unutbu    schedule 03.06.2019
comment
นี่อาจเป็นวิธีแก้ปัญหาที่ดี แต่มันค่อนข้างน่ารำคาญเนื่องจากฉันทำงานในธุรกรรม sqlalchemy ซึ่งฉันกำลังดำเนินการ orm เป็นประจำ (แต่ไม่เกี่ยวข้อง) - person Rémi Bonnet; 03.06.2019
comment
ฉันได้เพิ่มโค้ดบางส่วนเพื่อแสดงวิธีที่คุณสามารถใช้ psycopg2 เพื่อสร้าง SQL โดยไม่ต้องดำเนินการ จากนั้นคุณสามารถใช้ sqlalchemy เพื่อรัน SQL ได้ - person unutbu; 03.06.2019
comment
ขอบคุณ. ฉันได้ดูด้านข้างของฉันบ้างแล้ว และดูเหมือนว่าฉันสามารถเข้าถึงการเชื่อมต่อธุรกรรมปัจจุบันของฉันได้โดยทำ session.connection.connection หากเป็นกรณีนี้ โดยพื้นฐานแล้วจะช่วยแก้ปัญหาของฉันได้ ฉันจะปล่อยให้สิ่งนี้เปิดอีกสองสามชั่วโมงเพื่อดูว่าการตอบสนองของแพนด้า / sqlalchemy ที่บริสุทธิ์กว่านี้ปรากฏขึ้นหรือไม่ แต่ดูเหมือนว่าวิธีแก้ปัญหาของคุณจะเหมาะสม - person Rémi Bonnet; 03.06.2019