還在寫落落長的SQL語法嗎?
別鬧了,試試ORM框架吧!防堵SQL injection人人有責!
本文以Python運用SQLAlchemy框架連接Microsoft SQL Server
在網路上已經有許多關於ORM的優缺點文章,在此就不多贅述,若非需要很複雜的查詢,ORM還是我的首選,那麼問題來了,在.NET有Entity Framework,Python有什麼呢?
詳細介紹可以參考http://python.jobbole.com/84100/
本文所使用的是SQLAlchemy,並實作一個簡單的學生資料系統,藉此於Python中,以ORM技術完成對資料庫的CRUD操作
參考文章https://blog.csdn.net/billvsme/article/details/50197197
作業系統:Windows 10
Python版本:Python 3.6
使用套件:SQLAlchemy 1.2.8、pyodbc 4.0.23
SQLAlchemy需要藉由DBAPI和MS SQL溝通,
詳情可參考http://docs.sqlalchemy.org/en/latest/dialects/mssql.html
這裡我用的是pyodbc,也因為這樣所以加裝pyodbc 4.0.23
前置作業
首先呢資料庫長這樣
其中School資料表裡面已經先新增四所學校
然後介面長這樣,介面怎麼製作可以看Python建立圖形使用者介面的神兵利器-PyQt5
1.安裝
- 在CMD裡用pip指令安裝SQLAlchemy、pyodbc套件
-
pip install SQLAlchemy
-
pip install pyodbc
2.設定資料庫連線
-
開啟你的GUI主程式,import create_engine,並設定資料庫連線
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) self.show() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 而在其中呢......
- ODBC SQL Server 驅動程式版本可以參考
3.建立對應資料庫的Class
-
import declarative_base、SQL Server的資料庫型態、Table、 MetaData、 Column、 Integer、 String、 ForeignKey
-
撰寫對應資料庫的Class並且完成映射
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) self.show() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下這些東西
4.建立連線物件Session
- import sessionmaker
- 將session與engine繫結
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) self.show() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下這些東西
5.撰寫新增功能
- import uuid因為新增需要ID,之後的功能也需要
- 為新增按鈕的點擊事件綁定function
- 自輸入框取得資料並放進物件
- 將物件存於記憶體
- commit確認完成修改資料庫
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker import uuid engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) #為新增按鈕的點擊事件綁定function self.ui.CButton.clicked.connect(self.CButton_Clicked) self.show() def CButton_Clicked(self): #依照校名找出該校ID tschool = session.query(School).filter(School.Name==self.ui.CSchoolTextBox.text()).one() #將各資料塞進物件 newstudent = Student(ID=uuid.uuid4(), Name=self.ui.CNameTextBox.text(), Sex=self.ui.CSexTextBox.text(), School=tschool.ID) #將物件存於記憶體 session.add(newstudent) # 這裡是一次增加一筆 # 若將來需要增加多筆可以改成以下寫法 # session.add_all([ # Student(ID=uuid.uuid4(), Name='Student1', School='School1', Sex='男生'), # Student(ID=uuid.uuid4(), Name='Student2', School='School2', Sex='女生'), # Student(ID=uuid.uuid4(), Name='Student3', School='School3', Sex='女生')]) #確認修改資料庫 session.commit() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下東西
6.撰寫查詢功能
- 使用join多表查詢
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker import uuid engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) #為新增按鈕的點擊事件綁定function self.ui.CButton.clicked.connect(self.CButton_Clicked) #為查詢按鈕的點擊事件綁定function self.ui.RButton.clicked.connect(self.RButton_Clicked) self.show() def CButton_Clicked(self): #依照校名找出該校資料 tschool = session.query(School).filter(School.Name==self.ui.CSchoolTextBox.text()).one() #將各資料塞進物件 newstudent = Student(ID=uuid.uuid4(), Name=self.ui.CNameTextBox.text(), Sex=self.ui.CSexTextBox.text(), School=tschool.ID) #將物件存於記憶體 session.add(newstudent) # 這裡是一次增加一筆 # 若將來需要增加多筆可以改成以下寫法 # session.add_all([ # Student(ID=uuid.uuid4(), Name='Student1', School='School1', Sex='男生'), # Student(ID=uuid.uuid4(), Name='Student2', School='School2', Sex='女生'), # Student(ID=uuid.uuid4(), Name='Student3', School='School3', Sex='女生')]) #確認修改資料庫 session.commit() def RButton_Clicked(self): #依照學生姓名撈出該名學生所有資料,使用join tstudent = session.query(Student,School).join(School,Student.School==School.ID).filter(Student.Name==self.ui.RNameTextbox.text()).one() self.ui.lineEdit.setText('ID:'+tstudent.Student.ID+',性名:'+tstudent.Student.Name+ ',性別:'+tstudent.Student.Sex+',學校:'+tstudent.School.Name) app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下東西
7.撰寫修改功能
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker import uuid engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) #為新增按鈕的點擊事件綁定function self.ui.CButton.clicked.connect(self.CButton_Clicked) #為查詢按鈕的點擊事件綁定function self.ui.RButton.clicked.connect(self.RButton_Clicked) #為修改按鈕的點擊事件綁定function self.ui.UButton.clicked.connect(self.UButton_Clicked) self.show() def CButton_Clicked(self): #依照校名找出該校資料 tschool = session.query(School).filter(School.Name==self.ui.CSchoolTextBox.text()).one() #將各資料塞進物件 newstudent = Student(ID=uuid.uuid4(), Name=self.ui.CNameTextBox.text(), Sex=self.ui.CSexTextBox.text(), School=tschool.ID) #將物件存於記憶體 session.add(newstudent) # 這裡是一次增加一筆 # 若將來需要增加多筆可以改成以下寫法 # session.add_all([ # Student(ID=uuid.uuid4(), Name='Student1', School='School1', Sex='男生'), # Student(ID=uuid.uuid4(), Name='Student2', School='School2', Sex='女生'), # Student(ID=uuid.uuid4(), Name='Student3', School='School3', Sex='女生')]) #確認修改資料庫 session.commit() def RButton_Clicked(self): #依照學生姓名撈出該名學生所有資料,使用join tstudent = session.query(Student,School).join(School,Student.School==School.ID).filter(Student.Name==self.ui.RNameTextbox.text()).one() self.ui.lineEdit.setText('ID:'+tstudent.Student.ID+',性名:'+tstudent.Student.Name+ ',性別:'+tstudent.Student.Sex+',學校:'+tstudent.School.Name) def UButton_Clicked(self): #根據ID找出該名學生資料 tstudent = session.query(Student).filter(Student.ID==self.ui.UIDTextbox.text()).one() #根據學校名稱找出該校資料 tschool = session.query(School).filter(School.Name==self.ui.USchoolTextbox.text()).one() #將資料更新 tstudent.Name = self.ui.UNameTextbox.text() tstudent.Sex = self.ui.USexTextbox.text() tstudent.School = tschool.ID #確認修改資料庫 session.commit() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下東西
8.撰寫刪除功能
-
import sys from PyQt5.QtWidgets import QDialog, QApplication from UI import Ui_Form from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.dialects.mssql import \ BIGINT, BINARY, BIT, CHAR, DATE, DATETIME, DATETIME2, \ DATETIMEOFFSET, DECIMAL, FLOAT, IMAGE, INTEGER, MONEY, \ NCHAR, NTEXT, NUMERIC, NVARCHAR, REAL, SMALLDATETIME, \ SMALLINT, SMALLMONEY, SQL_VARIANT, TEXT, TIME, \ TIMESTAMP, TINYINT, UNIQUEIDENTIFIER, VARBINARY, VARCHAR from sqlalchemy import Table, MetaData, Column, Integer, String, ForeignKey from sqlalchemy.orm import mapper from sqlalchemy.orm import sessionmaker import uuid engine = create_engine('mssql+pyodbc://DemoUsername:DemoPassword@DESKTOP-T40QFVL/Demo?driver=SQL+Server+Native+Client+11.0') Base = declarative_base() #建立school資料表對應 school_metadata = MetaData() school = Table('School', school_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(8)) ) class School(object): def __init__(self, ID, Name): self.ID = ID self.Name = Name mapper(School, school) #建立student資料表對應 student_metadata = MetaData() student = Table('Student', student_metadata, Column('ID', UNIQUEIDENTIFIER, primary_key=True), Column('Name', NVARCHAR(10)), Column('School', UNIQUEIDENTIFIER, ForeignKey(School.ID)), Column('Sex', NVARCHAR(5)) ) class Student(object): def __init__(self, ID, Name, School, Sex): self.ID = ID self.Name = Name self.School = School self.Sex = Sex mapper(Student, student) Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() class AppWindow(QDialog): def __init__(self): super().__init__() self.ui = Ui_Form() self.ui.setupUi(self) #為新增按鈕的點擊事件綁定function self.ui.CButton.clicked.connect(self.CButton_Clicked) #為查詢按鈕的點擊事件綁定function self.ui.RButton.clicked.connect(self.RButton_Clicked) #為修改按鈕的點擊事件綁定function self.ui.UButton.clicked.connect(self.UButton_Clicked) #為修改按鈕的點擊事件綁定function self.ui.DButton.clicked.connect(self.DButton_Clicked) self.show() def CButton_Clicked(self): #依照校名找出該校資料 tschool = session.query(School).filter(School.Name==self.ui.CSchoolTextBox.text()).one() #將各資料塞進物件 newstudent = Student(ID=uuid.uuid4(), Name=self.ui.CNameTextBox.text(), Sex=self.ui.CSexTextBox.text(), School=tschool.ID) #將物件存於記憶體 session.add(newstudent) # 這裡是一次增加一筆 # 若將來需要增加多筆可以改成以下寫法 # session.add_all([ # Student(ID=uuid.uuid4(), Name='Student1', School='School1', Sex='男生'), # Student(ID=uuid.uuid4(), Name='Student2', School='School2', Sex='女生'), # Student(ID=uuid.uuid4(), Name='Student3', School='School3', Sex='女生')]) #確認修改資料庫 session.commit() def RButton_Clicked(self): #依照學生姓名撈出該名學生所有資料,使用join tstudent = session.query(Student,School).join(School,Student.School==School.ID).filter(Student.Name==self.ui.RNameTextbox.text()).one() self.ui.lineEdit.setText('ID:'+tstudent.Student.ID+',性名:'+tstudent.Student.Name+ ',性別:'+tstudent.Student.Sex+',學校:'+tstudent.School.Name) def UButton_Clicked(self): #根據ID找出該名學生資料 tstudent = session.query(Student).filter(Student.ID==self.ui.UIDTextbox.text()).one() #根據學校名稱找出該校資料 tschool = session.query(School).filter(School.Name==self.ui.USchoolTextbox.text()).one() #將資料更新 tstudent.Name = self.ui.UNameTextbox.text() tstudent.Sex = self.ui.USexTextbox.text() tstudent.School = tschool.ID #確認修改資料庫 session.commit() def DButton_Clicked(self): #根據ID找出該名學生資料 tstudent = session.query(Student).filter(Student.ID==self.ui.DIDTextbox.text()).one() #於記憶體標記刪除動作 session.delete(tstudent) #確認修改資料庫 session.commit() app = QApplication(sys.argv) w = AppWindow() w.show() sys.exit(app.exec_())
- 新增以下東西
成品
- 新增
- 查詢
- 修改
- 刪除