วิธีดำเนินการ SQL ดิบในแอพ Flask-SQLAlchemy


219

คุณรัน SQL ดิบใน SQLAlchemy ได้อย่างไร?

ฉันมีเว็บแอพไพ ธ อนที่ทำงานบนขวดและส่วนต่อประสานกับฐานข้อมูลผ่าน SQLAlchemy

ฉันต้องการวิธีเรียกใช้ SQL ดิบ แบบสอบถามเกี่ยวข้องกับการรวมหลายตารางพร้อมกับมุมมองแบบอินไลน์

ฉันพยายามแล้ว:

connection = db.session.connection()
connection.execute( <sql here> )

แต่ฉันได้รับข้อผิดพลาดเกตเวย์


5
ฉันเคยดูมาก่อน แต่ฉันไม่พบบทเรียนเกี่ยวกับการอัปเดต ฉันยังไม่ควรเรียนรู้ไวยากรณ์และซ่อนแบบสอบถาม SQL ที่ค่อนข้างยาว (ประมาณ 20 บรรทัด)
starwing123

103
@MarkusUnterwaditzer ฉันเคยคิดแบบนั้น แต่ตอนนี้ฉันไม่เห็นด้วยอย่างยิ่ง โดยทั่วไปแล้ว SQL แบบ parametrized ที่ถูกต้องจะอ่านและดูแลได้ง่ายกว่าฟังก์ชั่นการโทรและวัตถุที่สร้างขึ้น มันยังช่วยให้คุณมีความสามารถเต็มรูปแบบของฐานข้อมูลโดยไม่ต้องผ่านการวนซ้ำเพื่อให้ ORM สร้างไวยากรณ์ที่ถูกต้อง (ถ้าเป็นไปได้) และป้องกันไม่ให้ ORM ทำสิ่งที่ไม่คาดคิด คุณอาจถามคำถาม "ทำไมจึงใช้ SQLAlchemy เลย?" และคำตอบเดียวที่ฉันมีคือ "แอปพลิเคชันที่มีอยู่ใช้งานและเปลี่ยนทุกอย่างแพงเกินไป"
jpmc26

4
@ jpmc26 เพิ่มความคิดเห็นของคุณ - ในฐานะคนรักของ SQL ฉันมีช่วงเวลาที่ยากลำบากกับความคิดที่ว่า "มอบกุญแจสู่ฐานข้อมูล" ให้กับนักเล่นแร่แปรธาตุที่ขาดความรับผิดชอบและมีแนวโน้มที่จะอยู่ข้างORM เป็นปฏิปักษ์ :) กล่าวว่าฉันกระตือรือร้นที่จะเร่งส่วนประกอบบางอย่างเช่นการลงทะเบียนผู้ใช้ / การจัดการและการสร้างตารางที่มีลำดับของปุ่มที่ฉันสามารถเขียนโค้ดการกระทำ + SQL คุณเคยเจอเครื่องมือที่เป็นมิตรกับ ORM ที่สงสัยว่าทำงานได้ดีสำหรับคุณในกรอบ Python หรือไม่?
zx81

@ jpmc26 คุณใช้อะไรในเฟรมเวิร์ก Python เพื่อใช้งาน SQL หรือใกล้เคียงกับ C # Dapper ทุกอย่างที่ฉันเห็นในกรอบงานเว็บของ Python ต้องการให้ฉันใช้ SQLAlchemy และฉันไม่ชอบ ORM และถ้าฉันใช้อย่างใดอย่างหนึ่งมันก็น้อยมาก
johnny

@ Johnny ฉันไม่ได้มีโอกาสลองด้วยตัวเอง แต่ไลบรารีการเชื่อมต่อฐานข้อมูลดิบอาจจะเพียงพอ ยกตัวอย่างเช่น psycopg2 มีเคอร์เซอร์ว่าการกลับมาnamedtupleและdictโดยตรง: initd.org/psycopg/docs/extras.html
jpmc26

คำตอบ:


310

คุณเคยลองไหม:

result = db.engine.execute("<sql here>")

หรือ:

from sqlalchemy import text

sql = text('select name from penguins')
result = db.engine.execute(sql)
names = [row[0] for row in result]
print names

7
หากคุณใส่หรืออัปเดตคุณจะยอมรับธุรกรรมได้อย่างไร
David S

14
หากคุณใช้ raw SQL คุณก็สามารถควบคุมธุรกรรมได้ดังนั้นคุณต้องออกคำสั่งBEGINและCOMMITstatement ด้วยตัวเอง
Miguel

1
คำสั่ง SQL เดียวกันทำงานเมื่อคุณออกโดยไม่มี SQLAlchemy หรือไม่? คุณอาจต้องการเปิดใช้งานการดีบักในฐานข้อมูลของคุณเพื่อให้คุณสามารถดูคำสั่งที่กำลังดำเนินการ
Miguel

27
db.engine.execute(text("<sql here>")).execution_options(autocommit=True))ดำเนินการและกระทำด้วย
Devi

8
@Miguel "หากคุณใช้ SQL ดิบคุณสามารถควบคุมธุรกรรมได้ดังนั้นคุณต้องออกคำสั่ง BEGIN และ COMMIT ด้วยตนเอง" นี้เป็นเพียงไม่เป็นความจริง. คุณสามารถใช้ raw SQL กับวัตถุเซสชัน เพิ่งสังเกตเห็นความคิดเห็นนี้ แต่คุณสามารถดูคำตอบของฉันสำหรับวิธีการใช้เซสชันกับ SQL ดิบ
jpmc26

180

วัตถุเซสชัน SQL Alchemy มีexecuteวิธีการของตนเอง:

result = db.session.execute('SELECT * FROM my_table WHERE my_column = :val', {'val': 5})

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

ยังรับทราบว่าexecuteถูกออกแบบมาสำหรับแบบสอบถาม ใช้พารามิเตอร์เช่น:valในตัวอย่างสำหรับอินพุตใด ๆ ในเคียวรีเพื่อป้องกันตัวคุณจากการโจมตีของการฉีด SQL คุณสามารถระบุค่าสำหรับพารามิเตอร์เหล่านี้โดยส่งผ่านdictเป็นอาร์กิวเมนต์ที่สองโดยที่แต่ละคีย์คือชื่อของพารามิเตอร์ตามที่ปรากฏในแบบสอบถาม ไวยากรณ์ที่แน่นอนของพารามิเตอร์นั้นอาจแตกต่างกันไปขึ้นอยู่กับฐานข้อมูลของคุณ แต่ฐานข้อมูลเชิงสัมพันธ์ที่สำคัญทั้งหมดสนับสนุนในบางรูปแบบ

สมมติว่ามันเป็นSELECTแบบสอบถามนี้จะกลับiterableของRowProxyวัตถุ

คุณสามารถเข้าถึงแต่ละคอลัมน์ด้วยเทคนิคที่หลากหลาย:

for r in result:
    print(r[0]) # Access by positional index
    print(r['my_column']) # Access by column name as a string
    r_dict = dict(r.items()) # convert to dict keyed by column names

โดยส่วนตัวแล้วฉันชอบที่จะแปลงผลลัพธ์เป็นnamedtuples:

from collections import namedtuple

Record = namedtuple('Record', result.keys())
records = [Record(*r) for r in result.fetchall()]
for r in records:
    print(r.my_column)
    print(r)

หากคุณไม่ได้ใช้ส่วนขยาย Flask-SQLAlchemy คุณยังสามารถใช้เซสชันได้อย่างง่ายดาย:

import sqlalchemy
from sqlalchemy.orm import sessionmaker, scoped_session

engine = sqlalchemy.create_engine('my connection string')
Session = scoped_session(sessionmaker(bind=engine))

s = Session()
result = s.execute('SELECT * FROM my_table WHERE my_column = :val', {'val': 5})

เลือกจะส่งกลับ ResultProxy
อลัน B

@AlanB ใช่ ฉันเลือกคำของฉันไม่ดีเมื่อฉันเรียกมันว่าลำดับซึ่งหมายความว่ามันใช้โปรโตคอลลำดับ ฉันได้รับการแก้ไขและชี้แจง ขอบคุณ
jpmc26

@ jpmc26 ควรปิดเซสชันหลังจากดำเนินการสืบค้นเช่น db.session.close ()? และจะยังคงมีประโยชน์ในการรวมการเชื่อมต่อหรือไม่
ravi malhotra

58

เอกสาร: บทช่วยสอนเกี่ยวกับภาษา Expression SQL - การใช้ข้อความ

ตัวอย่าง:

from sqlalchemy.sql import text

connection = engine.connect()

# recommended
cmd = 'select * from Employees where EmployeeGroup = :group'
employeeGroup = 'Staff'
employees = connection.execute(text(cmd), group = employeeGroup)

# or - wee more difficult to interpret the command
employeeGroup = 'Staff'
employees = connection.execute(
                  text('select * from Employees where EmployeeGroup = :group'), 
                  group = employeeGroup)

# or - notice the requirement to quote 'Staff'
employees = connection.execute(
                  text("select * from Employees where EmployeeGroup = 'Staff'"))


for employee in employees: logger.debug(employee)
# output
(0, 'Tim', 'Gurra', 'Staff', '991-509-9284')
(1, 'Jim', 'Carey', 'Staff', '832-252-1910')
(2, 'Lee', 'Asher', 'Staff', '897-747-1564')
(3, 'Ben', 'Hayes', 'Staff', '584-255-2631')

1
ลิงก์ไปยังเอกสาร sqlalchemy ดูเหมือนจะล้าสมัย นี่เป็นสิ่งล่าสุดเมื่อเร็ว ๆ นี้: docs.sqlalchemy.org/en/latest/core/…
Carl

1
ฉันขอถามได้==ไหมว่าทำไมเราถึงใช้?
Nam G VU

1
@ Jake Berger ขอบคุณมากสำหรับคุณ ฉันเสียเวลาค้นหาคำตอบนี้เกือบทั้งวัน ฉันเพิ่งดำเนินการ sql โดยตรงโดยไม่แปลงเป็นข้อความ มันเกิดข้อผิดพลาดเมื่อเรามี% นักเรียน% ในประโยคของฉัน ปรบมืออย่างยิ่งใหญ่สำหรับคำตอบของคุณ
Suresh Kumar

1
@NamGVU เพราะเหมือนในภาษาการเขียนโปรแกรมส่วนใหญ่=มักจะสงวนไว้สำหรับการกำหนดค่า; ในขณะที่==สงวนไว้สำหรับการเปรียบเทียบ ค่า
Jake Berger

2
@ JakeBerger คุณมีลิงค์สำหรับสิ่งนั้นหรือไม่? SQL ไม่ใช่ภาษาและตัดสินโดยSQLAlchemy docsซึ่งไม่เป็นเช่นนั้น
johndodo

36

คุณจะได้รับผลของการค้นหา SELECT SQL ใช้from_statement()และtext()ตามที่แสดงไว้ที่นี่ คุณไม่ต้องจัดการกับสิ่งอันดับด้วยวิธีนี้ เป็นตัวอย่างสำหรับชั้นเรียนที่Userมีชื่อตารางusersคุณสามารถลอง

from sqlalchemy.sql import text
.
.
.
user = session.query(User).from_statement(
    text("SELECT * FROM users where name=:name")).\
    params(name='ed').all()

return user

15
result = db.engine.execute(text("<sql here>"))

ดำเนินการ<sql here>แต่ไม่ได้กระทำเว้นแต่ว่าคุณจะอยู่ในautocommitโหมด ดังนั้นการแทรกและการอัพเดทจะไม่สะท้อนในฐานข้อมูล

หากต้องการส่งมอบหลังจากการเปลี่ยนแปลงให้ทำ

result = db.engine.execute(text("<sql here>").execution_options(autocommit=True))

2

นี่เป็นคำตอบที่ง่ายกว่าของวิธีเรียกใช้แบบสอบถาม SQL จาก Flask Shell

ขั้นแรกให้จับคู่โมดูลของคุณ (หากโมดูล / แอปของคุณคือ Manage.py ในโฟลเดอร์หลักและคุณอยู่ในระบบปฏิบัติการ UNIX) ให้เรียกใช้:

export FLASK_APP=manage

เรียกใช้เปลือกขวด

flask shell

นำเข้าสิ่งที่เราต้องการ ::

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
from sqlalchemy import text

เรียกใช้แบบสอบถามของคุณ:

result = db.engine.execute(text("<sql here>").execution_options(autocommit=True))

ใช้การเชื่อมต่อฐานข้อมูลปัจจุบันซึ่งมีแอปพลิเคชัน


0

คุณลองใช้connection.execute(text( <sql here> ), <bind params here> )และผูกพารามิเตอร์ตามที่อธิบายไว้ในเอกสารหรือไม่ สิ่งนี้สามารถช่วยแก้ปัญหาการจัดรูปแบบพารามิเตอร์และปัญหาประสิทธิภาพการทำงาน บางทีข้อผิดพลาดของเกตเวย์คือการหมดเวลาหรือไม่ พารามิเตอร์การผูกมักจะทำให้การสืบค้นที่ซับซ้อนดำเนินการเร็วขึ้นอย่างมาก


2
ตามเอกสารconnection.execute(text(<sql here>), <bind params> )ที่ควรจะเป็น ไม่ควรจะอยู่ในbind params การให้อาหารในพารามิเตอร์การเชื่อมโยงกับวิธี execute ()text()
Jake Berger

ลิงก์ของ Jake ขาด ฉันคิดว่านี่เป็น URL ที่เกี่ยวข้องตอนนี้: docs.sqlalchemy.org/en/latest/core/…
code_dredd

-1

หากคุณต้องการที่จะหลีกเลี่ยง tuples, วิธีอื่นโดยการเรียกfirst, oneหรือallวิธีการ:

query = db.engine.execute("SELECT * FROM blogs "
                           "WHERE id = 1 ")

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