import sqlalchemy
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# สร้างคลาสแบบจำลองพื้นฐาน
Base = declarative_base()
# สร้างคลาสตารางฐานข้อมูลนักเรียน
class Nakrian(Base):
__tablename__ = 'nakrian'
chue = sqlalchemy.Column(sqlalchemy.String,primary_key=True)
ayu = sqlalchemy.Column(sqlalchemy.Integer)
sung = sqlalchemy.Column(sqlalchemy.Float)
def __repr__(self):
return f'{self.chue} อายุ {self.ayu} สูง {self.sung*100:.0f} ซม.'
# สร้างตัวเชื่อมต่อกับฐานข้อมูล sqlite ในไฟล์ yurudata.db
engine = sqlalchemy.create_engine('sqlite:///yurudata.db')
# ทำการสร้างตารางขึ้นมาในฐานข้อมูล sql จริงๆ
Base.metadata.create_all(engine)
# สร้างเซชชัน
sm = sessionmaker(engine)
session = sm()
# เข้าสู่ขั้นตอนใช้งาน
# สร้างออบเจ็กต์นักเรียนคนแรก
nakrian1 = Nakrian(chue='ริเสะ',ayu=15,sung=1.46)
# ใส่นักเรียนคนแรกลงในฐานข้อมูล
session.add(nakrian1)
# สร้างออบเจ็กต์นักเรียนคนที่ ๒ และ ๓
nakrian2 = Nakrian(chue='อายาโนะ',ayu=14,sung=1.59)
nakrian3 = Nakrian(chue='ซากุราโกะ',ayu=13,sung=1.53)
# ใส่นักเรียนคนที่ ๒ และ ๓ ลงในฐานข้อมูลพร้อมกันทีเดียว
session.bulk_save_objects([nakrian2,nakrian3])
# บันทึกความเปลี่ยนแปลงลงในฐานข้อมูลจริงๆ
session.commit()
# เปิดดูข้อมูลทั้งหมดในฐานข้อมูล
for nakrian in session.query(Nakrian).order_by(Nakrian.ayu):
print(nakrian)
ซากุราโกะ อายุ 13 สูง 153 ซม.
อายาโนะ อายุ 14 สูง 159 ซม.
ริเสะ อายุ 15 สูง 146 ซม.
sqlalchemy.ext.declarative.declarative_base()
sqlalchemy.create_engine()
แล้วระบุชื่อฐานข้อมูลที่จะเชื่อมต่อ sqlalchemy.create_engine('sqlite:///พาธและชื่อไฟล์ฐานข้อมูล')
Base.metadata.create_all(engine)
sqlalchemy.orm.sessionmaker()
ชื่อฟังก์ชัน | หน้าที่ |
---|---|
sqlalchemy.create_engine |
สร้างตัวเชื่อมต่อกับฐานข้อมูล |
sqlalchemy.orm.sessionmaker |
ตัวสร้างเซสชันการเชื่อมต่อ |
sqlalchemy.ext.declarative.declarative_base |
ตัวสร้างคลาสที่เป็นฐานของแบบจำลองตารางข้อมูล |
sqlalchemy.Column |
สร้างออบเจ็กต์ที่แทนสดมภ์ของตารางข้อมูล |
sqlalchemy.String sqlalchemy.Integer sqlalchemy.Float ฯลฯ |
คลาสแทนชนิดข้อมูล สายอักขระ, เลขจำนวนเต็ม, เลขทศนิยม, ฯลฯ |
Base
ซึ่งสร้างขึ้นมาจาก sqlalchemy.ext.declarative.declarative_base()
อีกทีimport sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
Basemodel = declarative_base()
class Phukla(Basemodel):
__tablename__ = 'phukla'
chue = sqlalchemy.Column(sqlalchemy.String(32),primary_key=True)
lv = sqlalchemy.Column(sqlalchemy.Integer)
hp = sqlalchemy.Column(sqlalchemy.Float)
def __repr__(self):
return f'{self.chue} lv: {self.lv} hp: {self.hp}'
engine = sqlalchemy.create_engine('sqlite:///pukla.db')
Basemodel.metadata.create_all(engine)
sqlalchemy.Column()
คือคลาสที่ใช้สร้างออบเจ็กต์สดมภ์ (column) ของตารางข้อมูล ต้องการให้ตารางมีข้อมูลอะไรบ้างก็ใส่ไปตามนั้น โดยในวงเล็บจะเป็นตัวกำหนดคุณสมบัติของสดมภ์นั้น ที่สำคัญที่สุดก็คือชนิดของข้อมูล ก็ให้ใส่โดยใช้ตัวคลาสที่เตรียมไว้ในตัวมอดูล sqlalchemy ได้แก่ String
(สายอักขระ), Integer
(เลขจำนวนเต็ม), Float
(เลขทศนิยม), ฯลฯsqlalchemy.String(20)
แบบนี้ แต่จะไม่ใส่ก็ได้primary_key=True
ลงไปphukla1 = Phukla(chue='ได',lv=99,hp=999)
print(phukla1) # ได้ ได lv: 99 hp: 999
__repr__
นี้ใส่ลงไปเพื่อกำหนดว่าเวลาสั่ง print
ขึ้นมาจะให้แสดงค่าเป็นแบบไหน อาจจะไม่ได้จำเป็นต้องใส่ แต่สะดวกเวลาแสดงผล จะได้รู้ว่าข้อมูลตัวนี้เป็นตัวไหน มีค่าอะไรอย่างไร__repr__
เมื่อสั่ง print
ก็คงจะออกมาในลักษณะแบบนี้<__main__.Phukla at 0x11b2a5090></__main__.Phukla>
__repr__
ไปด้วยตลอด__init__
เพื่อให้ตอนสร้างง่ายขึ้นหน่อย เช่นแค่ใส่เป็นอาร์กิวเมนต์ลงไปตามลำดับ รวมทั้งกำหนดค่าเริ่มต้นให้ได้ด้วย เช่นclass Nangsue(Basemodel):
__tablename__ = 'nangsue'
chue = sqlalchemy.Column(sqlalchemy.String,primary_key=True)
chamnuanna = sqlalchemy.Column(sqlalchemy.Integer)
rakha = sqlalchemy.Column(sqlalchemy.Float)
def __init__(self,chue,chamnuanna=100,rakha=200):
self.chue = chue
self.chamnuanna = chamnuanna
self.rakha = rakha
def __repr__(self):
return f'หนังสือชื่อ "{self.chue}" มี {self.chamnuanna} หน้า ราคา {self.rakha} บาท'
ns = Nangsue('สอนแมวเขียนโปรแกรม',400)
print(ns) # ได้ หนังสือชื่อ "สอนแมวเขียนโปรแกรม" มี 400 หน้า ราคา 200 บาท
Base.metadata.create_all()
Base
นี้ก็คือตัวคลาสฐานที่ใช้ตอนรับทอดคลาสตารางข้อมูลนี้มา หากมีข้อมูลที่อยากให้สร้างขึ้นมาพร้อมกันก็ให้รับทอดจาก Base
อันนี้เหมือนกัน แล้วเมื่อสั่ง .metadata.create_all()
ก็จะสร้างขึ้นมาใหม่ทั้งหมด ถ้าหากยังไม่ได้มีอยู่ก่อน แต่ถ้าอันไหนมีแล้วก็จะไม่เกิดอะไรขึ้นBase.metadata.drop_all()
sqlalchemy.orm.sessionmaker
import
เอาตัวคลาสและเซสชันมาใช้import sqlalchemy
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Basemodel = declarative_base()
class Pokemon(Basemodel):
__tablename__ = 'pokemon'
lek = sqlalchemy.Column(sqlalchemy.Integer,primary_key=True)
chue = sqlalchemy.Column(sqlalchemy.String(32))
nak = sqlalchemy.Column(sqlalchemy.Float)
sung = sqlalchemy.Column(sqlalchemy.Float)
def __init__(self,lek,chue,nak,sung):
self.lek = lek
self.chue = chue
self.nak = nak
self.sung = sung
def __repr__(self):
return f'{self.lek}. {self.chue} หนัก {self.nak} สูง {self.sung*100:.0f} ซม.'
engine = sqlalchemy.create_engine('sqlite:///pkdata.db')
Basemodel.metadata.create_all(engine)
Session = sessionmaker(engine)
session = Session()
session.add
(ถ้ามีตัวเดียว) หรือ session.add_all
หรือ session.bulk_save_objects
(ถ้ามีหลายตัว)session.commit()
เพื่อให้ข้อมูลถูกบันทึกไปจริงๆimport
ตัวคลาสแบบจำลองและ session จาก pkmoses ที่สร้างขึ้นมาจากไฟล์ที่สร้างจากในหัวข้อที่แล้ว แล้วเพิ่มข้อมูลลงไปตัวหนึ่งด้วย .add()
from pkmoses import session,Pokemon
poke1 = Pokemon(152,'ชิโครีตา',6.4,0.9)
session.add(poke1)
session.commit()
.add_all()
หรือ .bulk_save_objects()
ก็ได้poke2 = Pokemon(155,'ฮิโนอาราชิ',7.9,0.5)
poke3 = Pokemon(158,'วานิโนโกะ',9.5,0.6)
session.bulk_save_objects([poke2,poke3])
session.commit()
poke4 = Pokemon(153,'เบย์ลีฟ',15.8,1.2)
poke5 = Pokemon(154,'เมกาเนียม',100.5,1.8)
session.add_all([poke4,poke5])
session.commit()
.add_all()
จะเป็นการสั่งให้วนซ้ำเพิ่มข้อมูลเข้าไปทีละตัวตามลำดับ ส่วน .bulk_save_objects()
จะทำไปพร้อมกันและจะรวดเร็วกว่า.bulk_save_objects()
จะเร็วกว่า เพียงแต่เนื่องจากข้อมูลอาจถูกใส่อย่างไม่เป็นลำดับถูกต้อง ดังนั้นหากให้ความสำคัญกับลำดับของข้อมูลก็อาจใช้ .add_all()
.commit()
หากไม่มีการสั่ง .commit()
ไปแม้จะใช้ .add()
หรือ .add_all()
หรือ .bulk_save_objects()
ลงไปแล้วก็จะไม่มีการบันทึกลงไปจริงๆ.commit()
ก็ทำได้โดยตอนที่สร้างตัวสร้างเซสชันขึ้นให้ใส่เป็น sessionmaker(autocommit=True)
ไป ก็เป็นวิธีหนึ่งที่ทำได้.commit()
แล้วต้องการยกเลิกความเปลี่ยนแปลงนั้นก็สามารถทำได้โดยใช้ .rollback()
.query()
โดยใส่คลาสของตารางนั้นลงไปfor
เพื่อเอาค่าทีละตัวก็ได้for pk in session.query(Pokemon):
print(pk)
152. ชิโครีตา หนัก 6.4 สูง 90 ซม.
153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม.
154. เมกาเนียม หนัก 100.5 สูง 180 ซม.
155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม.
158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.
for pk in session.query(Pokemon.lek,Pokemon.chue):
print(pk)
(152, 'ชิโครีตา')
(153, 'เบย์ลีฟ')
(154, 'เมกาเนียม')
(155, 'ฮิโนอาราชิ')
(158, 'วานิโนโกะ')
for
แล้วก็ยังอาจจะใช้ .all()
เพื่อดึงข้อมูลมาทั้งหมดทีเดียวเป็นลิสต์ก็ได้print(session.query(Pokemon).all())
print(session.query(Pokemon.lek,Pokemon.chue).all())
[152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
[(152, 'ชิโครีตา'), (153, 'เบย์ลีฟ'), (154, 'เมกาเนียม'), (155, 'ฮิโนอาราชิ'), (158, 'วานิโนโกะ')]
.order_by()
โดยระบุสดมภ์ที่ต้องการใช้เรียงลงไป เช่นprint(session.query(Pokemon).order_by(Pokemon.sung).all())
print(session.query(Pokemon).order_by(Pokemon.nak).all())
[155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม., 152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.]
[152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.]
.desc()
เช่นprint(session.query(Pokemon).order_by(Pokemon.lek.desc()).all())
print(session.query(Pokemon).order_by(Pokemon.sung.desc()).all())
[158. วานิโนโกะ หนัก 9.5 สูง 60 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 152. ชิโครีตา หนัก 6.4 สูง 90 ซม.]
[154. เมกาเนียม หนัก 100.5 สูง 180 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม.]
.first()
เช่นprint(session.query(Pokemon).first())
# ได้ 152. ชิโครีตา หนัก 6.4 สูง 90 ซม.
print(session.query(Pokemon).order_by(Pokemon.sung).first())
# ได้ 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม.
print(session.query(Pokemon).order_by(Pokemon.nak.desc()).first())
# ได้ 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.
.limit()
และกำหนดลำดับเริ่มต้นโดยใช้ .offset()
เช่นprint(session.query(Pokemon).limit(3).all())
# ได้ [152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.]
print(session.query(Pokemon.lek).limit(4).all())
# ได้ [(152,), (153,), (154,), (155,)]
print(session.query(Pokemon).offset(2).all())
# ได้ [154. เมกาเนียม หนัก 100.5 สูง 180 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
print(session.query(Pokemon).offset(1).limit(2).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.]
print(session.query(Pokemon).order_by(Pokemon.sung).limit(2).all())
# ได้ [155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
order_by
เพื่อกำหนดรูปแบบการเรียงไปด้วยให้ใส่ order_by
ก่อนค่อยตามด้วย offset
และ limit
.filter()
เช่นprint(session.query(Pokemon).filter(Pokemon.nak<10).all())
# ได้ [152. ชิโครีตา หนัก 6.4 สูง 90 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
print(session.query(Pokemon.chue).filter(Pokemon.chue!='เบย์ลีฟ').all())
# ได้ [('ชิโครีตา',), ('เมกาเนียม',), ('ฮิโนอาราชิ',), ('วานิโนโกะ',)]
print(session.query(Pokemon).filter(Pokemon.nak>8,Pokemon.nak<20).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
print(session.query(Pokemon.chue).filter(Pokemon.chue!='ชิโครีตา',Pokemon.chue!='เบย์ลีฟ').all())
# [('เมกาเนียม',), ('ฮิโนอาราชิ',), ('วานิโนโกะ',)]
and_
, or_
, not_
เพื่อแทน "และ", "หรือ", "ไม่"print(session.query(Pokemon).filter(sqlalchemy.or_(Pokemon.chue=="เบย์ลีฟ",Pokemon.chue=="เมกาเนียม")).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม.]
print(session.query(Pokemon.chue).filter(sqlalchemy.not_(Pokemon.chue=="ชิโครีตา")).all())
# ได้ [('เบย์ลีฟ',), ('เมกาเนียม',), ('ฮิโนอาราชิ',), ('วานิโนโกะ',)]
and_
, or_
, not_
มี _
ต่อท้ายเพราะคำว่า and
, or
, not
เป็นคำสงวนในไพธอน ใช้เป็นชื่อตัวแปรไม่ได้เลยต้องเติม _
.between()
เพื่อคัดเอาค่าที่อยู่ในช่วงได้print(session.query(Pokemon).filter(Pokemon.nak.between(9,25)).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 158. วานิโนโกะ หนัก 9.5 สูง 60 ซม.]
print(session.query(Pokemon).filter(Pokemon.lek.between(153,155)).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 154. เมกาเนียม หนัก 100.5 สูง 180 ซม., 155. ฮิโนอาราชิ หนัก 7.9 สูง 50 ซม.]
.contains()
เพื่อคัดเอาที่มีคำที่ต้องการอยู่print(session.query(Pokemon.chue).filter(Pokemon.chue.contains('โน')).all())
# ได้ [('ฮิโนอาราชิ',), ('วานิโนโกะ',)]
.query()
อ่านข้อมูลเข้ามา แล้วเอามาแก้ แล้วก็ทำการ .commit()
ข้อมูลในฐานข้อมูลก็จะถูกแก้ไปด้วยตามนั้นpk1 = session.query(Pokemon).filter(Pokemon.chue=='ฮิโนอาราชิ').first()
pk1.lek = 156
pk1.chue = 'แม็กมาราชิ'
pk1.nak = 0.9
pk1.sung = 19
session.commit()
.update()
ที่ตัวข้อมูลที่ใช้ .filter()
คัดมา เพื่อสั่งแก้ข้อมูลโดยตรง เช่นkha = {'lek':159,'chue':'อาลิเกตซ์','nak':1.1,'sung':25}
session.query(Pokemon).filter(Pokemon.chue=='วานิโนโกะ').update(kha)
session.commit()
.query()
ดึงข้อมูลที่ต้องการลบออกมา แล้วใช้เมธอด .delete()
จากตัวเซสชัน แล้ว .commit()
pk2 = session.query(Pokemon).filter(Pokemon.chue=='เมกาเนียม').first()
session.delete(pk2)
session.commit()
.delete()
จากตัว query โดยตรงเลยก็ได้ เช่นsession.query(Pokemon).filter(Pokemon.chue=='ชิโครีตา').delete()
session.commit()
.commit()
ที่ตัวเซสชันอาจสามารถย้อนคืนกลับได้โดยใช้ .rollback()
print(session.query(Pokemon).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 156. แม็กมาราชิ หนัก 0.9 สูง 1900 ซม., 159. อาลิเกตซ์ หนัก 1.1 สูง 2500 ซม.]
kha = {'lek':157,'chue':'บักฟูน','nak':1.7,'sung':79.5}
session.query(Pokemon).filter(Pokemon.chue=='แม็กมาราชิ').update(kha)
session.query(Pokemon).filter(Pokemon.chue=='เบย์ลีฟ').delete()
print(session.query(Pokemon).all())
# ได้ [157. บักฟูน หนัก 1.7 สูง 7950 ซม., 159. อาลิเกตซ์ หนัก 1.1 สูง 2500 ซม.]
session.rollback()
print(session.query(Pokemon).all())
# ได้ [153. เบย์ลีฟ หนัก 15.8 สูง 120 ซม., 156. แม็กมาราชิ หนัก 0.9 สูง 1900 ซม., 159. อาลิเกตซ์ หนัก 1.1 สูง 2500 ซม.]
try:
oda = Pokemon(160,'ออร์ไดล์','ไม่ทราบ',2.3)
session.add(oda)
session.commit()
except sqlalchemy.exc.SQLAlchemyError as e:
session.rollback()
print('เกิดข้อผิดพลาด\n',e)
เกิดข้อผิดพลาด
(builtins.ValueError) could not convert string to float: 'ไม่ทราบ'
[SQL: INSERT INTO pokemon (lek, chue, nak, sung) VALUES (?, ?, ?, ?)]
[parameters: [{'sung': 2.3, 'lek': 160, 'chue': 'ออร์ไดล์', 'nak': 'ไม่ทราบ'}]]
sqlalchemy.exc.SQLAlchemyError
คือคลาสของความผิดพลาดที่จะเกิดขึ้นเมื่อมีปัญหาในการติดต่อกับฐานข้อมูล sql จะมีการอธิบายรายละเอียดว่าโค้ด sql ที่ส่งไปจริงๆเป็นอย่างไร และผิดพลาดที่ตรงไหนnak
ว่า 'ไม่ทราบ'
ทั้งที่จริงๆควรจะเป็นตัวเลข ก็เลยเกิดข้อผิดพลาดขึ้น.execute()
ซึ่งอาจเรียกจากตัว engine หรือตัว session ก็ได้.execute()
ทำแทนได้ เช่นimport sqlalchemy
from sqlalchemy.orm import sessionmaker
engine = sqlalchemy.create_engine('sqlite:///pikadata.db')
engine.execute('create table pokemon (lek integer,chue text,nak real,sung real)')
engine.execute('insert into pokemon (lek,chue,nak,sung) values (25,"พิคาชู",6,0.4)')
print(engine.execute('select * from pokemon').fetchall())
# ได้ [(25, 'พิคาชู', 6.0, 0.4)]
execute
กับ select
ก็จะเอาผลที่ได้มาใช้ .fetchall()
หรือวนด้วย for
เพื่อเอาข้อมูลได้ เหมือนกับที่ใช้ sqlite3 โดยตรง.execute()
สั่งไปทั้งหมดแบบนี้ก็ไม่ต่างจากใช้ sqlite3 โดยตรง อาจไม่มีความหมายที่จะใช้ sqlalchemy นัก.execute()
อาจจะใช้แค่บางส่วน แค่ในกรณีที่ต้องการสั่งคำสั่งที่ไม่สะดวกจะทำผ่านเมธอดต่างๆของ sqlalchemy เอง ซึ่งอาจมีความจำเป็นบ้างเพราะโค้ดจับคู่เชื่อมต่อก็ไม่ได้ทำทุกอย่างไว้สมบูรณ์แบบทั้งหมด