Flask-論壇開發-3-數據庫

對Flask感興趣的,能夠看下這個視頻教程:http://study.163.com/course/courseLearn.htm?courseId=1004091002前端

1. SQLAlchemy 鏈接數據庫

要在 python 中鏈接數據庫,則要從 sqlalchemy 中導入 create_engine,而且要配置好數據庫的信息,以下代碼所示:python

# 導入模塊
from sqlalchemy import create_engine

# 配置數據庫
DIALECT = 'mysql'
DRIVER = 'mysqldb'  # python2 寫 mysqldb;python3 寫 pymysl
USERNAME = 'root'
PASSWORD = 'root'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'db_demo1'

# DB_URL 的格式是:dialect+driver://username:password@host:port/database
# 因此要將配置變量組合成固定格式
DB_URL = "mysql+mysqldb://{username}:{password}@{host}:{port}/{db}?charset=utf8".format(username=USERNAME,password=PASSWORD,host=HOST,port=PORT,db=DATABASE)

# 建立數據庫引擎
engine = create_engine(DB_URL)

# 判斷是否鏈接成功
conn = engine.connect()

注意,以上方法未涉及 Flask 的內容,包括 17. Flask 下使用 SQLalchemy 節以前的內容,只是在純 python 代碼中經過 sqlalchemy 進行數據庫的操做。mysql

2. ORM 介紹

ORM``(Object Relationship Mapping):對象關係映射。實際上就是模型與數據庫表的映射。sql

3. 數據庫命令:

drop database db_name;              刪除數據庫
create database db_name charset utf8;       建立數據庫
show tables;                    查詢數據庫中的全部 table
drop table person;                  刪除名稱爲 person 的 table
desc person;                    查看 person 的具體屬性

2. Flask 中使用 sqlalchemy 鏈接數據庫

2.1 鏈接數據庫

  1. 要在 Flask 中使用 sqlalchemy 鏈接數據庫,應該先導入 create_engine數據庫

    from sqlalchemy import create_engine
  2. 再作好鏈接數據庫的相關配置:flask

    HOSTNAME = '127.0.0.1'
     PORT = '3306'
     DATABASE = 'mydb01'
     USERNAME = 'root'
     PASSWORD = 'root'
    
     # dialect+driver://username:password@host:port/database
     DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)
  3. 鏈接數據庫安全

    engine = create_engine(DB_URL)

2.2 建立 ORM 模型並映射到數據庫中

  1. 建立 ORM 模型服務器

    要建立 ORM 模型,這個模型必須繼承自 sqlalchemy 給咱們定義好的基類。這個基類是經過 sqlalchemy.ext.declarative 下的一個函數(declarative_base)來初始化的,因此咱們要先導入它:session

    from sqlalchemy.ext.declarative import declarative_base

    導入完成後,還須要使用它進行基類的初始化,即便用它建立一個基類:app

    Base =  declarative_base(engine)
     # 指定數據庫引擎建立一個基類,並賦給 Base 變量

    其中,基類和類與對象之間的關係是:基類建立類 -> 類實例化出對象。建立這個基類,是由於這個基類已經幫咱們封裝好了映射到數據庫中的一些方法,自定義的模型若繼承自這個基類,會更方便咱們經過 python 去操做數據庫。

    完成以上步驟後,就能夠經過建立出來的基類再建立一個類,而這個類就是 ORM 中的模型,以下:

    class Person(Base):     # 必須繼承自基類
         __tablename__ = 'person'    # 使用 __tablename__ 來指定映射到數據庫中的表名
  2. 定義模型屬性,即定義數據庫表的字段

    上一步的代碼,只是建立了一個能夠映射到數據庫中的一個表,可是該表並無任何字段,咱們須要完善其中的屬性。而這些屬性在數據庫中是一個個的數據類型(如 int | char | varchar 等),這些數據類型也在 sqlalchemy 中定義好了,咱們能夠直接用,但要先導入:

    from sqlalchemy import Column,Integer,String
    
     class Person(Base):
         __tablename__ = 'person'
         id = Column(Integer,primary_key=True,autoincrement=True)    # 一個 Column 能夠定義表中的一個列,能夠在括號內指定數據類型(Integer),主鍵,自增加等數據庫屬性
         name = Column(String(10))
         age = Column(Integer)
  3. 將建立好的模型映射到數據庫中

    Base.metadata.create_all()

    須要注意的是:一旦使用 Base.metadata.create_all() 將模型映射到數據庫中,以後若要改變表中的字段(添加字段或刪除字段)再從新映射,那麼是不會生效的。

  4. 去數據庫中驗證是否成功

    show tables;
  5. 完整代碼以下:

    from sqlalchemy import create_engine,Column,Integer,String
     from sqlalchemy.ext.declarative import declarative_base
    
     HOSTNAME = '127.0.0.1'
     PORT = '3306'
     DATABASE = 'mydb01'
     USERNAME = 'root'
     PASSWORD = 'root'
    
     # dialect+driver://username:password@host:port/database
     DB_URL = 'mysql+pymysql://{username}:{password}@{host}:{port}/{db}?charset:utf8'.format(username=USERNAME,password=PASSWORD,host=HOSTNAME,port=PORT,db=DATABASE)
    
     engine = create_engine(DB_URL)
    
     Base =  declarative_base(engine)
    
     # 1. 建立一個 ORM 模型,這個模型必須繼承自 sqlalchemy 給咱們定義好的基類
     class Person(Base):
         __tablename__ = 'person'
         # 2. 在這個 ORM 模型中建立一些屬性,對應於表中的一些字段,而這些屬性必須是 sqlalchemy 給咱們定義好的數據類型
         id = Column(Integer,primary_key=True,autoincrement=True)
         name = Column(String(10))
         age = Column(Integer)
     # 3. 將建立好的 ORM 模型映射到數據庫中
     Base.metadata.create_all()
  6. 魔術方法

在定義一個類的時候,能夠定義一個 __str__() 方法,這個方法的做用是:當這個類被 print 方法調用時,那麼 print 會首先調用這個類裏面的 __str__() 方法。而在 __str__() 方法中,通常都是 return 一個字符串。例如,對剛剛的類定義的 __str__() 方法:

class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(10))
    age = Column(Integer)

    def __str__(self):
        return 'Person(name:%s,age:%s)' % (self.name,self.age)

3. 增刪改查

全部的數據庫 ORM 操做都必須經過一個叫作 session 的會話對象來實現,那麼這個對象的建立是經過如下代碼來實現的:

from sqlalchemy.orm import sessionmaker
engine = create_engine(DB_URL)
Session = sessionmaker(engine)
session = Session()

其中:sessionmakerdeclarative_base 的原理相似,也是一個方法。後者接收一個 engine 建立一個基類,再建立一個模型;前者也要接收一個 engine ,從而對 engine 中的數據進行操做(增刪改查等)。

後兩行代碼能夠簡化寫成:session = sessionmaker(engine)()

3.1 增

建立對象,並使用 session 對象添加且提交:

p = Person(name='myyd',age=18)  # 建立對象(對於自增加的屬性,主鍵不用寫)
session.add(p)      # 將對象添加到會話對象中
session.commit()    # 使用 commit 將會話中的對象提交到數據庫中

若是要添加多條數據,則必須以列表的形式傳給 session 對象的 add_all() 方法:

p1 = Person(name='MYYD',age=19)
p2 = Person(name='Myyd',age=20)
session.add_all([p1,p2])    # 注意是 add_all 
session.commit()

3.2 查

能夠查詢所某個數據庫中某個表的全部數據,也可使用條件查詢該表中的符合條件的數據。

  1. 查找表中的全部數據:

    person_all = session.query(Person).all()
     for person in person_all:
         print(person)
  2. 查找表中符合條件的全部數據(方法一):

    person_all = session.query(Person).filter_by(age=18).all()
     for person in person_all:
         print(person)
  3. 查找表中符合條件的全部數據(方法二):

    person_all = session.query(Person).filter(Person.age==18).all()
     for person in person_all:
         print(person)
  4. 查找表中符合條件的全部數據(區別):

    區別在於,filter() 要指定類名,而且判斷時要使用雙等於號 ==,要相對麻煩一點。可是這兩種方法,在大項目中是會同時用到的,因此兩個都要學會!

  5. 使用 get() 方法根據主鍵查找數據:

    get() 方法會根據表中的主鍵進行查找數據,如有則返回數據,若無則返回 None。

    person1 = session.query(Person).get(1)
     person2 = session.query(Person).get(100)
     print(person1,person2)
     # 會返回 get(1) 的數據,get(100) 的數據是 None
  6. 使用 first() 方法獲取表中的第一條數據:

    person3 = session.query(Person).first()
     print(person3)

3.3 改

要修改表中的數據,思路很簡單:先經過查詢,將指定數據選出來並賦予一個變量;再修改該變量的屬性;最後用 session.commit() 提交便可。以下所示:

person = session.query(Person).first()
person.name = 'mayiyoudu'
session.add(person)
session.commit()

3.4 刪

和修改相似,先查詢找到指定的數據,再經過 session 進行刪除。以下所示:

person = session.query(Person).first()
session.delete(person)
session.commit()

4. Sqlalchemy 的經常使用數據類型 和 ORM 模型經常使用參數

4.1 經常使用數據類型

  1. 整型(Integer),有微整型,整型和長整型
  2. 浮點型(Float,Double),Float32 位;Double64
  3. 布爾型(Boolean),在數據庫中能夠用微整型(01)來實現
  4. 定點類型(DECIMAL),用來處理精度丟失的問題,至關於將輸入的浮點數當成文本處理
  5. 枚舉類型(Enum),只能輸入預先指定的內容
  6. 日期類型(Date),只能存儲年月日,傳入datetime.date()
  7. 日期時間類型(DateTime),能夠存儲年月日時分秒,傳入datetime.datetime()
  8. 時間類型(Time),只能存儲時分秒,傳入datetime.time()
  9. 字符類型(String),至關於數據庫中的varchar類型,便可變長字符類型
  10. 文本類型(Text),至關於數據庫中的text類型,最多隻能存儲6W多個字
  11. 長文本類型(LONGTEXT),若是文字較多,可用LONGTEXT類型,只MySQL支持,要從另外的包中導入

下面咱們分別來介紹各個數據類型的特性:

  1. 整型、浮點型、文本類型、字符類型比較經常使用,咱們放到一個例子來說。以下代碼所示:

    class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         title = Column(String(50),nullable=True)
         content = Column(Text,nullable=True)
         price = Column(Float,nullable=True)
    
     article = Articles(title='MYYD',content='HelloWorld',price=12.34563)

    上述代碼表示:

    1. id 字段爲整型,其值必須爲一個整數;
    2. title 字段爲字符類型,對應數據庫中的 varchar 類型,是一個可變長度的字符,括號中的數字爲該字段所能接受的最大字母數;
    3. content 字段爲文本類型,可接受最大字符長度爲6W多字;
    4. price 字段爲浮點類型,對於 Float 來講只能表示4位小數,對於 Double 來講能夠接受8位小數,若是數字太大,可使用定點類型。
  2. 定點類型

    class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         price = Column(DECIMAL(10,6))
    
     article = Articles(price=2345.67891)

    其中,DECIMAL(10,6) 表明一共只能表示10個數字,分別是:整數最多隻能有4位,小數最多隻能表示6位。若是小數位數多了則四捨五入表示6位;若是整數位數多了,則直接報錯。即小數位數能夠大於6而整數位數不能夠大於4。

  3. 日期時間類型

    from datetime import date,datetime,time
    
     class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         create_date = Column(Date)
         create_datetime = Column(DateTime)
         create_time = Column(Time)
    
     article = Articles(create_date=date(2011,11,11),create_datetime=datetime(2011,11,11,11,11,11),create_time=time(11,11,11))

    要使用日期和時間,須要另外從 datetime 模塊中導入 datedatetimetime;與模型字段中的三個數據類型 DateDatetiemTime 一一對應。

  4. 枚舉類型(方法一)

    class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         Language = Column(Enum('Python','Java','PHP','C++'))
    
     article = Articles(Language='python')

    其中,在建立實例的時候,Language 字段必需要從枚舉指定的 Python Java PHP C++ 中選擇,不然報錯。

  5. 枚舉類型(方法二)

    定義枚舉類型還有另一種更好的方法,就是藉助 python3 中自帶的 enum 模塊進行定義。要注意四個地方:

    1. 導入 enum 模塊
    2. 建立所需的 enum 類並繼承自 enum.Enum
    3. 建立模型而且使用枚舉數據類型時,從自定義的 enum 類中引用
    4. 根據模型建立實例時也能夠從自定義的 enum 類中取

    以下代碼所示:

    import enum
    
     class MyEnum(enum.Enum):
         Python = 'Python'
         Java = 'Java'
         PHP = 'PHP'
         C = 'C++'
    
     class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         Language = Column(Enum(MyEnum))
    
     article = Articles(Language=MyEnum.Python)
  6. 長文本類型

    長文本類型只有 MySQL 數據庫才支持,因此若是想使用長文本類型,則須要從 sqlalchemy.dialects.mysql 中導入:

    from sqlalchemy.dialects.mysql import LONGTEXT
    
     class Articles(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         content = Column(LONGTEXT)
    
     article = Articles(content='over 60000 words')

4.2 經常使用參數

  1. primary_key:設置是否爲主鍵
  2. autoincrement:設置是否爲自增加
  3. default:設置默認值,即當實例化的時候沒有指定該屬性的值時,該屬性的值。能夠在 create_time 屬性中使用。
  4. nullable:設置該屬性的值是否能夠爲空,若是沒有給該屬性設置該參數,則默認爲 True,即默承認空。但主鍵默認爲 False
  5. unique:設置是否惟一,好比手機號碼、郵箱等屬性,都是惟一的,即要指定 uniqueTrue。不設置時,默認是 False
  6. onupdate:若設置了該屬性,則當其餘屬性有改動的時候,該屬性也會更改。最典型的應用是:

    update_time 用來設置文章的更新時間,當文章的標題或者內容被更新時,update_time 也會隨之被更新,以下代碼所示:

    class Aricles(Base)
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True)
         title = Column(String(50),nullable=False)
         content = Column(Text,nullable=Flase)
         update_time=Column(DateTime,onupdate=datetime.now,default=datetime.now)
    
     article = Articles(datetime.now())

    當對象 article 被建立後,在某一時刻其 title 或者 content 屬性被修改,那麼其 update_time 屬性因爲被指定了 onupdate=datetime.now 參數,也會隨之更改。

    在第一次建立這條數據的時候,不會使用 onupdate 的值,而是使用 default 的值。
  7. name:用來指定某個模型中的屬性映射到數據庫後,該屬性對應字段的名稱。也就是說,你在定義模型的時候,有一個 title 屬性,可是你想讓該屬性映射到數據庫中的時候變成其餘名字的字段,就可使用 name 參數來實現。如:

    class Aricles(Base)
         id = Column(Integer,primary_key=True)
         __tablename__ = 'articles'
         title = Column(String(50),nullable=False,name='My_title')
    
     # 若是把 name 參數放到該屬性第一個位置,則不須要 name 關鍵字,以下便可:
         title = Column('My_title',String(50),nullable=False)
    
     # 可是不能夠把該參數放到第一個位置的同時還指定參數名,由於關鍵字參數必需要放在未知參數以後!(name='myyd'這種叫關鍵字參數;int(12)這種叫未知參數)。
         title = Column(name='My_title',String(50),nullable=False)   # 這樣是不行的

5. query 查詢詳解

準備工做:

在查詢以前,咱們須要在建立好的模型中定義 __repr__() 函數,這個函數的做用是:當用 print 輸出由這個類組成的列表時,會按照這個函數定義的格式輸出。

要注意與 __str__() 函數的區別,__str__() 函數是隻有在 print 輸出去這個類的具體實例的時候纔會被調用。

兩個函數的定義實現以下:

def __str__(self):
    return 'id=%s;title=%s;price=%s' % (self.id,self.title,self.price)
def __repr__(self):
    return 'id=%s;title=%s;price=%s' % (self.id, self.title, self.price)

5.1 獲取模型全部屬性

能夠用 query 獲取 ORM 模型中實例的全部屬性:

result = session.query(Articles).all()
for article in result:
    print(article)

5.2 獲取模型中指定屬性

能夠用 query 獲取 ORM 模型中實例的指定屬性:

result = session.query(Articles.title,Articles.price).all()
for article in result:
    print(article)

5.3 聚合函數

能夠用 query 內置的一些聚合函數來實現對查找到的數據作進一步的操做,在使用這些聚合函數以前要先導入這些函數所在的類(func)。

from sqlalchemy import func

這些聚合函數是:

  1. func.count:統計行的數量

    session.query(func.count(Articles.id)).first()  # 注意要指定表的字段,即模型的屬性
     # 輸出該表中的數據條目數量,例如:有六條數據就輸出(6,)
  2. func.avg:求平均值

    求平均值也是相似的用法:

    result = session.query(func.avg(Articles.price)).first()
     print(result)   # (78.28571428571429,)
  3. func.max:求最大值

    result = session.query(func.max(Articles.price)).first()
     print(result)   # (97,)
  4. func.min:求最小值

    result = session.query(func.min(Articles.price)).first()
     print(result)   # (51,)
  5. func.sum:求和

    result = session.query(func.sum(Articles.price)).first()
     print(result)   # (548.0,)

實際上,func 對象中並無定義任何函數,由於它底層的實現是把傳入的函數翻譯成 sql 語句後再進行操做。因此只要是 mysql 中有的聚合函數,均可以使用 func. 來調用。

6. filter 方法經常使用過濾條件

  1. equal

    article = session.query(Articles).filter(Articles.id==1).all()
     print(article)
  2. not equal

    articles = session.query(Articles).filter(Articles.id!=1).all()
     print(articles)
  3. like & ilike(不區分大小寫)

    articles = session.query(Articles).filter(Articles.title.like('MYY%')).all()        # 其中,% 在 sql 語句中是通配符
     print(articles)
  4. in

    這裏要注意,使用的 in 的時候要傳入一個列表,最終查找的結果是:既在數據庫中又是列表中指定的數據被找到。

    articles = session.query(Articles).filter(Articles.title.in_(['MYYD','title1','title2'])).all()
     # 爲何是 in_ ?由於 python 中有關鍵字 in,爲了區分因此不能用 in,而 _in 表明了類中的私有方法,但這裏很明顯應該是公有方法,因此定義爲 in_
     print(articles)
  5. not in

    即相對於上例而言,取反的結果。

    方法一:波浪號

    articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2'])).all()        # 注意這裏要加一個波浪號
     print(articles)

    方法二:notin_()

    articles = session.query(Articles).filter(Articles.title.notin_(['MYYD','title1','title2'])).all()      # 注意這裏是 notin_()
     print(articles)
  6. is nullis not null

    用來根據某個字段是否爲空來查找數據,以下:

    articles = session.query(Articles).filter(Articles.content==None).all()
     print(articles)
    
     # 查找 Articles.content 字段爲空的數據

    is not null 的示例以下:

    articles = session.query(Articles).filter(Articles.content!=None).all()
     print(articles)
    
     # 查找 Articles.content 字段爲空的數據
  7. and

    用來查找更精細的範圍,如 content='abc' 而且 title='MYYD' 的數據,以下:

    articles = session.query(Articles).filter(Articles.content=='abc',Articles.title=='MYYD').all()
     print(articles)

    會查找到 title 爲 MYYD 而且 content 爲 abc 的數據條目。

  8. or

    只要知足指定條件之一便可。以下:

    from sqlalchemy import or_
    
     articles = session.query(Articles).filter(or_(Article.title=='MYYD',Articles.content=='MYYD')).all()
     print(articles)

    用得比較多的狀況是,搜索一個關鍵字,這個關鍵字可能出如今標題中,也可能出如今內容中。這裏要注意的是:要從 sqlalchemy 中導入 or_ 這個方法。

    實際上,and 也有這種方法,以下:

    from sqlalchemy impor and_
    
     articles = session.query(Aritlces).filter(and_(Articles.title=='MYYD',Articles.content=='abc')).all()
     pint(articles)
  9. 小知識,若是想要獲取翻譯成的 sql 語句,能夠在查詢的時候不加 .all() 或者 .first(),以下:

    articles = session.query(Articles).filter(~Articles.title.in_(['MYYD','title1','title2']))
     print(articles)
    
     輸出以下:
     SELECT articles.id AS articles_id, articles.title AS articles_title, articles.price AS articles_price FROM articles WHERE articles.title NOT IN (%(title_1)s, %(title_2)s, %(title_3)s, %(title_4)s)

7. 外鍵及其四種約束

外鍵可使表與表之間的關係更加緊密,Sqlalchemy 也支持對外鍵的操做。而 Sqlalchemy 操做外鍵是經過 ForeignKey 來實現的。最多見的例子是:有用戶和文章這兩張表,每張表都有本身的屬性,可是文章是經過用戶來發表的,因此這兩張表中必然存在某種聯繫;使用外鍵就能夠將兩張表聯繫起來。

那麼怎麼使用 sqlalchemy 建立兩張具備約束關係的表呢?以下所示:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(20),nullable=False)

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text)

    user_id = Column(Integer,ForeignKey('users.id'))    # 注意這個地方要使用數據庫表名,而不是建立模型時的類名

7.1 外鍵的四種約束

使外鍵有四個能夠約束的功能,在使用外鍵鏈接時用 ondelete 關鍵字指定。指定方法是:

user_id = Column(Integer,ForeignKey('users.id',ondelete='RESTRICT'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='NO ACTION'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))

四個約束功能介紹以下:

  1. RESTRICT:父表數據被刪除時會因爲子表數據還在引用而阻止刪除

    這個約束是默認就會存在的,不用特地指定。

  2. NO ACTION:與 MySQL 中的 RESTRICT 相同

    既然相同,那麼也不用特地指定。

  3. CASCADE:級聯刪除,即當父表中的某個條目被刪除了,子表中關聯了該條目的數據也會被刪除

    user_id = Column(Integer,ForeignKey('users.id',ondelete='CASCADE'))
  4. SET NULL:父表中的某個條目被刪除,子表中關聯了該條目的數據不會被刪除,可是外鍵字段會被置爲 NULL

    user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))

    即原來是:

    mysql> select * from articles;
     +----+-------+---------+---------+
     | id | title | content | user_id |
     +----+-------+---------+---------+
     |  1 | NB    | abc     |       1 |
     +----+-------+---------+---------+

    users 表中 id=1 的數據被刪除後,articles 表中關於該字段的值就會被置爲 NULL

    mysql> select * from articles;
     +----+-------+---------+---------+
     | id | title | content | user_id |
     +----+-------+---------+---------+
     |  1 | NB    | abc     |    NULL |
     +----+-------+---------+---------+

    要注意的是,在使用外鍵時,是不能夠爲其設置 SET NULL 字段的同時還設置 nullable=False 的。

8. ORM 外鍵和一對多關係

如今有一個需求,要查找某一篇文章標題爲 NB 的做者的信息,怎麼才能實現這一需求?

8.1 原始的方法:

  1. 查找標題爲 NB 的文章
  2. 獲取該文章關聯到 users 的外鍵字段的值
  3. 經過該值去 users 表中查找到該做者

代碼實現以下:

article_NB = session.query(Article).filter_by(title='NB').first()
print(article_NB)
uid = article_NB.user_id
author = session.query(User).get(uid)
print(author)

8.2 ORM 提供的方法(relationships)

要使用 ORM 提供的這個方法,就必須從 sqlalchemy.orm 模塊中導入:

from sqlalchemy.orm import sessionmaker,relationship

而後在定義外鍵的時候,同時定義外鍵所連接到的表的字段,並指定 relationship 字段:

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text)

    user_id = Column(Integer,ForeignKey('users.id'))    # 注意這個地方要使用數據庫表名,而不是建立模型時的類名

    author = relationship('User')   # 注意這個地方要使用建立模型時的類名,而不是數據庫表名

作好上述定之後,就能夠直接經過 relationships 提供的方法,來獲取文章的做者了,以下所示:

article = session.query(Article).filter(Article.title=='NB').first()
print(article.author)   # article.author 便是所要找的做者
print(article.author.username)  # 打印做者的用戶名

8.3 relationship 反向查找

上面的例子是:查找一篇文章對應的做者,那麼文章和做者之間的關係是多對一的,即一個做者可能發表多篇文章,而一篇文章只能有一個做者。

8.2 中,已經使用 relationship 引用了文章的做者,那麼可否使用 relationship 引用做者的文章呢?答案是確定的,以下所示:

from sqlalchemy.orm import sessionmaker,relationship

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(20),nullable=False)

    articles = relationship('Article')

經過做者找文章的方法和經過文章找做者的方法相似,以下:

user = session.query(User).filter_by(username='MYYD').filter()
print(user.articles)    # 會打印出該 user 的全部 article

8.4 使用 backref 反向引用

在以前的例子中,咱們在 UserArticle 中都分別使用了 relationship 來互相指定對方和本身的關係。實際上,不用這麼麻煩,咱們只須要在一個模型中指定其和另外一個模型的關係便可,這時須要藉助另外一個參數:bakeref

因此咱們只須要在 Article 模型中指定便可:

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text)

    user_id = Column(Integer,ForeignKey('users.id'))

    author = relationship('User',backref='articles')

這樣,一樣可使得 user.articles 可以正常使用。而不用再在 User 模型中使用 relationship 再指定一次關係了。

8.5 完整代碼

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(20),nullable=False)

    def __repr__(self):
        return 'username:%s'% self.username

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    content = Column(Text)
    user_id = Column(Integer,ForeignKey('users.id',ondelete='SET NULL'))
    author = relationship('User',backref='articles')

    def __repr__(self):
        return 'title:%s\tcontent:%s'%(self.title,self.content)

9. 一對一關係

在指定了一對多的關係後,如 usersarticles 這二者之間的關係。實際上,在建立 articles 的時候能夠不用指定其 user_id 字段的值,當一篇文章被建立完成後,再指定做者也是被容許的。由於在一對多的關係中,usersarticles 屬性是一個 LIST,可使用 append 方法爲其添加值。以下代碼所示:

article1 = Article(title='abc',content='abc')
article2 = Article(title='MYYD',content='myyd')
session.add_all([article1,article2])

user1 = User(username='MYYD')
session.add(user1)

user1.articles.append(article1)     # 使用 append() 方法
user1.articles.append(article2)
session.commit()

user = session.query(User).filter_by(username='MYYD').first()
print(user.articles)

# 輸出 [title:abc content:abc, title:MYYD content:myyd]

以上,是對 ORM 中一對多關係的一點補充。藉此,咱們能夠引出 ORM 中一對一的關係:

有一個需求:爲用戶建立一個 users 表,表中定義了一些屬性,這些屬性有:姓名,學校等。其中學校這個屬性不是經常使用屬性,咱們把它放到另一張 extends 表中。這樣一來,兩張表就是一對一的關係了。那麼,怎麼用 ORM 來實現這一需求呢?能夠在建立 extends 表的時候在使用 relationship 參數的字段中,傳入 uselist=False 參數,即不能夠 uselist,以下代碼所示:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

    def __repr__(self):
        return 'id:%s\tusername:%s'%(self.id,self.username)
class UserExtend(Base):
    __tablename__ = 'extends'
    id = Column(Integer,primary_key=True,autoincrement=True)
    school = Column(String(20))
    user_id = Column(Integer,ForeignKey('users.id'))
    my_user = relationship('User',backref=backref('extends',uselist=False))     # 注意這裏使用 relationship 參數的方法,和傳入 backref 與以前的區別

    def __repr__(self):
        return 'school:%s'%self.school

user1 = User(username='MYYD')
session.add(user1)

extend = UserExtend(school='JMU')
session.add(extend)

user1.extends.append(extend)    # 此時再使用 append 方法時就會報錯:AttributeError: 'NoneType' object has no attribute 'append'
session.commit()

這就是 ORM 中一對一關係的使用。

10. 多對多的關係

多對多關係的需求經常使用在文章與標籤上,譬如能夠按照內容劃分文章的標籤爲:音樂、體育、娛樂、搞笑等;而一篇文章可能涉及多個方面的內容,這時候文章和標籤就是多對多的關係。可是咱們使用 ORM 定義兩張表的多對多關係時,須要藉助一箇中間表來實現。

10.1 多對多的建立

爲兩張表建立多對多關係的步驟以下:

  1. 導入定義中間表所需的 Table 類:

    from sqlalchemy import Table
  2. 建立兩張表(artcielstags):

    按照常規的方法建立兩張表便可:

    class Article(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         title = Column(String(50),nullable=False)
         content = Column(Text)
    
         def __repr__(self):
             return 'title:%s\tcontent%s'%(self.title,self.content)
    
     class Tag(Base):
         __tablename__ = 'tags'
         id = Column(Integer,primary_key=Text,autoincrement=True)
         name = Column(String(50),nullable=False)
    
         def __repr__(self):
             return 'TagName:%s'%self.name
  3. 重點在這裏!建立中間表:

    這個中間表用來鏈接剛剛定義的兩張表,中間表必須繼承自這個 Table 類。而且還要使用 metadate 進行初始化,同時要設置聚合主鍵,以下所示:

    article_tag = Table(
         'article_tag',
         Base.metadata,
         Column('articles_id',Integer,ForeignKey('articles.id'),primary_key=True),
         Column('tags_id',Integer,ForeignKey('tags.id'),primary_key=True)
         # tag_id 是字段名稱,Integer 的類型要和本表外鍵引用的字段類型相同
     )

    其中:

    1. Base.metadata 用來對這個中間表進行初始化;
    2. 而後用 Column 對其進行相關字段的建立。
    3. 爲兩個字段同時設置主鍵,這樣就能兩張表中的惟一一篇文章。
  4. 使用 relationship 關聯兩張表:

    最後,還要在兩張表的其中一張表中使用 relationship 參數來互相關聯一下,同時指定 secondary 參數來指定經過實現多對多關係的中間表,以下:

    1. articles 表中使用:

      tags = relationship('Tag',backref='articles',secondary=article_tag)
    2. tags 表中使用:

      articles = relationship('Article',backref='tags',secondary=article_tag)

    須要注意的是:若是使用中間表來實現多對多的映射關係後,就沒必要在兩張被映射的表中指定外鍵關係了。由於已經經過 secondary 來指定中間表格了,而中間表格會實現外鍵約束。

10.2 多對多的使用

咱們先用定義的兩張表建立數據並關聯一下:

  1. 爲每張表建立兩個數據

    article1 = Article(title='ABC',content='ABC')
     article2 = Article(title='abc',content='abc')
    
     tag1 = Tag(name='tag1')
     tag2 = Tag(name='tag2')
  2. 爲文章添加標籤

    article1.tags.append(tag1)
     article1.tags.append(tag2)
  3. 添加並提交 session

    session.add_all([article1,article2])
     session.commit()
  4. 到數據庫中查找

    # articles 表
         mysql> select * from articles;
         +----+-------+---------+
         | id | title | content |
         +----+-------+---------+
         |  1 | ABC   | ABC     |
         |  2 | abc   | abc     |
         +----+-------+---------+
         2 rows in set (0.00 sec)
    
     # tags 表
         mysql> select * from tags;
         +----+------+
         | id | name |
         +----+------+
         |  1 | tag1 |
         |  2 | tag2 |
         +----+------+
         2 rows in set (0.00 sec)
    
     # article_tag 表
         mysql> select * from article_tag;
         +-------------+---------+
         | articles_id | tags_id |
         +-------------+---------+
         |           1 |       1 |
         |           2 |       1 |
         |           1 |       2 |
         |           2 |       2 |
         +-------------+---------+
         4 rows in set (0.00 sec)

在用咱們的 query 去查詢:

article = session.query(Article).first()
print(article.tags)
# [TagName:tag1, TagName:tag2]

tag = session.query(Tag).first()
print(tag.articles)
# [title:ABC    contentABC, title:abc   contentabc]

11. ORM 層面刪除數據注意事項

ORM 層面對錶中的數據進行刪除,好比 articles 表中的 uid 字段經過外鍵引用到 users 表種的 id 字段。在使用 sql 語句對 users 表中的某個數據進行刪除的時候,會因爲 articles 表中的外鍵引用,而拒絕刪除。

可是在 ORM 層面使用 python 代碼對錶中的數據進行刪除,那麼其實是能夠刪除成功的。刪除的結果是:users 表中該條數據被刪除,可是引用了該條數據的 articles 表中的數據關聯的 id 字段會被置爲空。

這是由於,ORM 在底層對這個操做的實現分爲兩步:先將 articles 表中該數據的 uid 字段置爲 NULL,再刪除 users 表中的數據。不過前提是,articles 中的 uid 字段容許設置爲空。

可是這個機制實際上並不怎麼好,由於這樣可能會致使誤刪除的發生。那如何避免這種狀況呢?實際上也很簡單:在 articles 的外鍵字段上,設置 nullable=False 便可。

總結:

ORM 層面對數據庫刪除操做,會無視 mysql 級別的外鍵約束,要想避免這種狀況發生,須要將外鍵字段設置爲 nullable=False

12. relationship 方法中的 cascade 參數

relationship 方法中的 cascade 參數能夠在建立關係的時候,指定一些屬性。cascade 參數一共有如下幾個值:

  1. save-update(默認屬性)

    這是一個默認屬性,即當不指定 cascade 參數的時候,默認 cascade='sace-update'。這個屬性的意思是:

    若是給 cascade 指定了 save-update 屬性,那麼在添加一條數據的時候,會同時添加該條數據關聯的其餘模型中的數據。例如:

    Article 模型在與 User 模型創建關係的時候,若是指定了 cascade='save-update' 屬性,那麼當使用 session.add() 添加 Article 實例(article)的時候,若是該實例關聯的 User 實例(user)已經建立,則將 user 也一併添加。即不用再使用 session.add(user) 進行添加。

    具體的使用方法以下:

    author = relationship('User',backref='articles',cascade="save-update")

    若是不想讓 sqlalchemy 自動執行這個操做,那麼能夠將 cascade 置爲空,即 cascade=""須要注意的是,這個屬性僅對 Article 這個模型生效。也就是說,置爲空後,session.add(article) 不會自動執行 session.add(user),可是 session.add(user) 仍是會自動執行 session.add(article)。明白?

  2. delete

    delete 的做用就是,當刪除一條數據的時候,會同時刪除該條數據關聯的其餘模型中的數據。

    其使用方法和 save-update 一致。

    注意,cascade 屬性能夠同時指定多個值,例如同時指定 save-updatedelete,能夠這麼寫:

    author = relationship('User',backref='articles',cascade="save-update,delete")

    若是想在關聯模型中反向使用該屬性,還能夠將 cascade 放到 backref() 方法中:

    author = relationship('User',backref=backref('articles',cascade="save-update,delete",cascade="save-update,delete"))
    
     # 若是 backref 只接收一個參數,能夠寫成 backref='參數',若是接收多個參數,能夠調用 backref() 方法
  3. delete-orphan

    若是父表中的相關數據被刪除,會使得子表中的數據的某個字段置爲空。若是指定了 cascade='delete-orphan',那麼因爲父表的數據不存在,子表中的數據也會被刪除。不過在指定 delete-orphan 的時候要同時指定 delete 屬性。那麼刪除父表數據帶動子表數據被刪除的操做也有多是 delete 屬性完成的,因此咱們能夠將父表數據中關於子表數據的字段置空,這樣子表的相關字段也會被置空,進而被刪除。這樣就能體現出 delete-orphan 的做用了。

    注意這個屬性只能在父表中指定,而且只能用在一對多或者多對多的關係中。

  4. merge(默認屬性)

    這個屬性不只是 relationship 方法中 cascade 的屬性,仍是 session 中的屬性。其做用是:改變數據庫中的值。例如:

    users 表的 ORM 模型以下:

    class User(Base):
         __tablename__ = 'users'
         id = Column(Integer,primary_key=True,autoincrement=True)
         name = Column(String(50),nullable=False)

    若表中已經存在一個用戶(user1):id=1,name='MYYD';若是再建立一個用戶(user2):id=1,name='myyd'。使用 session.add(user2) 後再 commit,會報錯。由於 id 是主鍵,session.add() 不容許添加主鍵重複的數據。

    那麼若是使用 session.merge(user2),則會用 user2id 去匹配 user 表中的 id 字段,並將匹配中的數據中的 name 改成 myyd

    實際上這個屬性用得比較少。由於這對於已經存在的數據,至關於修改操做,而修改操做徹底沒必要要使用這種方法。那麼這個方法的存在還有什麼意義呢?

    這個方法還真有本身存在的意義,由於這種方法能夠添加數據,而修改操做卻不能添加。使用 session.merge() 的時候,首先會判斷表中是否已經存在這條數據,若存在則修改;若不存在則添加。

    除此以外,還有一個做用:若是在父表的 cascade 中指定了這個屬性,那麼父表下全部關聯的子表,在使用 session.merge(父表數據) 的時候,也會實現這個屬性。

  5. expunge

    expunge 屬性也是刪除,用 session.expunge() 實現。session.expunge()session.delete() 的區別是:前者僅僅是將數據從 session 中刪除,並不會刪除數據庫中的內容。這是 expungesession 中的體現。

    而 expunge 在 cascade 中的體現和 merge 相似:當在父表的 cascade 中指定了這個屬性,那麼在使用 session.cascade(父表數據) 時,父表下全部關聯的子表,也會實現這個屬性。

  6. all

    若是在 cascade 中指定了 all 屬性,那麼就至關於包含了以上除了 delete-orphan 外的全部屬性。

13. sqlalchemy 中的三種排序方式

  1. order_by

    能夠在查詢的時候,使用 order_by 參數對查詢到的數據進行排序。默認排序是從小到大,從前到後,若是想使用降序排序則在前面加一個 - 號。

    articles = session.query(Article).order_by(-Article.create_time).all()
     for article in articles:
         print(article)

    或者使用 desc 進行排序:

    articles = session.query(Article).order_by(Article.create_time.desc()).all()
     for article in articles:
         print(article)

    或者還能夠直接傳入模型字段的名稱,搭配 - 號使用:

    articles = session.query(Article).order_by('-create_time').all()
     for article in articles:
         print(article)
  2. 模型中的 order_by

    若是對於一個常常須要排序的模型來講,在每次查詢的時候都使用 order_by 參數進行排序,會有點麻煩,因此咱們再定義模型的時候就能夠定義一個 order_by 屬性。以下:

    先在模型中定義 __mapper_args__ 屬性:

    __mapper_args__ = {
         'order_by':create_time.desc()
     }

    而後查詢的時候就沒必要指定 order_by 關鍵字了:

    articles = session.query(Article).all()
     for article in articles:
         print(article)

    輸出以下:

    title:zhenniubi create_time:2018-03-28 23:33:32
     title:MYYD      create_time:2018-03-28 23:33:07

    可是若是想在某一次查詢中不使用倒序排序呢?很簡單,在查詢的時候指定 order_by 關鍵字便可。

  3. relationship 中使用 order_by 參數

    能夠在定義 relationship 關係的時候直接指定父表下子表的排序方式,以下:

    class User(Base):
         __tablename__ = 'users'
         id = Column(Integer,primary_key=True,autoincrement=True)
         username = Column(String(50),nullable=False)
    
     class Article(Base):
         __tablename__ = 'articles'
         id = Column(Integer,primary_key=True,autoincrement=True)
         title = Column(String(50),nullable=False)
         create_time = Column(DateTime,nullable=False,default=datetime.now)
         uid = Column(Integer,ForeignKey('users.id'),nullable=False)
    
         author = relationship('User',backref=backref('articles',order_by=create_time.desc()))

    當搜索到一個 user 時,獲取 user 下的全部 articles,這些 articles 的排序方式就是 order_by=create_time.desc() 指定的倒序。以下:

    user = session.query(User).filter(User.id=='1').first()
     for article in user.articles:
         print(article)
    
     # 打印的內容以下:
     title:title2    create_time:2018-03-29 00:19:18
     title:title1    create_time:2018-03-29 00:19:13

14. sqlalchemy 對數據庫的 limit 、offset 和 切片 操做

14.1 limit 操做

limit 這個屬性能夠對每次查詢的結果數量進行限制,例如能夠在查詢時只取 10 個數據:session.query(Article).limit(10).all()

須要注意的是,使用以上操做進行數據的查詢時,默認是從第 1 條數據進行查找的。若是咱們想要從指定位置開始查找,可使用 offset 操做:

14.2 offset 操做

offset 這個屬性能夠指定咱們開始查找的位置,例如想要獲取第十一到第二十條數據,能夠這麼操做:session.query(Article).offset(10).limit(10).all()

這個 offsetlimit 誰先誰後無所謂。

14.3 切片操做

實際上,經過 articles = session.query(Article).all() 獲得的數據,articles 是一個列表變量,因此咱們能夠經過列表中的切片屬性,來獲取指定的內容。例如想要從獲取到的全部數據中提取指定範圍內的數據(如第3個到第7個數據),有兩種方法能夠實現:

  1. slice 方法

    這種方法是:article = session.query(Article).slice(2,7).all()

  2. list 切片方法

    這種方法是:articles = session.query(Article).all()[2:7]

14.4 總結

以上三個操做,均可以搭配上一節中提到的排序方法靈活使用。

15. sqlalchemy 數據查詢懶加載技術

15.1 lazy='dynamic'

在現實的項目中,經常會遇到這種需求:當我點進一個用戶的主頁中,該用戶的主頁會顯示當天發表的文章,而不是其全部的文章。要實現這個功能實際上很簡單:先將該用戶的全部文章都查找出來,而後對每一篇的建立時間進行過濾後再渲染回前端。以下代碼所示:

articles = session.query(Article).all()
for article in articles:
    if article.create_time.day == 29:
        print(article)

以上方法,確實簡單粗暴。可是我只須要展現該用戶當天的文章,實際上不必從數據庫中查找出全部的文章,畢竟這麼作很浪費資源。咱們可使用 sqlalchemy 給咱們提供的懶加載技術,實現從數據庫中查找出來的文章就是當天發表的文章的需求。

可是咱們使用 articles = session.query(Article).all() 獲取到的對象是一個 LIST,而 LIST 是沒有辦法進行進一步的 filter 的。可是咱們的 Query 對象是能夠進行 filter 的,因此咱們能夠將 articles 轉換成 Query 對象,而後再對 articles 進行過濾。想要實現這個功能,那麼在建立模型關係的時候必需要在 relationship 中的 backref() 方法中,添加一個 lazy=dynamic 值。以下:

class Article(Base):
    略..
    author = relationship('User',backref=backref('articles',lazy='dynamic'))

而後使用 articles = session.query(Article).all() 查找到的 articles 就是一個 AppenderQuery 對象,該對象除了能夠實現 Query 對象的全部功能(包括filter),還能實現 LIST 的全部功能(包括append)。

因此這個需求咱們就能垂手可得的實現了:

user = session.query(User).filter_by(id=1).first()
print(type(user.articles))
    # 輸出:<class 'sqlalchemy.orm.dynamic.AppenderQuery'>

print(user.articles.filter(Article.create_time.day==29).all())
    # 輸出全部 29 號發表的文章
    # 注意這裏是去數據庫中篩選符合條件的數據,而不是將全部的數據都取出來,這樣就節省了資源的消耗

注意:這個地方 lazy='dynamic' 只能對一對多 或者 多對多 中的 使用,即對於上述兩個模型,只能用在 UserArticle 上,也就是說對符合條件的文章的過濾只能經過 user.articles.filter() 操做進行實現。

附:相對完整的代碼以下:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    create_time = Column(DateTime,nullable=False,default=datetime.now)
    uid = Column(Integer,ForeignKey('users.id'),nullable=False)

    author = relationship('User',backref=backref('articles',lazy='dynamic'))    # 注意這裏

user = session.query(User).first()
print(user.articles.filter(Article.id>9).all())

15.2 lazy='select'

這個是 sqlalchemy 中的默認選項,即若是不設置 lazy='xxx' 這個屬性,則默認爲 lazy='select' 生效。

lazy='select' 的做用是,在沒有調用 user.articles 以前,sqlalchemy 不會去獲取 user.articles 這個屬性下的數據;而一旦調用了 user.articles,那麼 sqlalchemy 就會自動的去獲取 user.articles 這個屬性下的全部數據。

這也就是爲何咱們在實現上述需求的時候,使用 lazy='dynamic' 的另外一大緣由。

16. sqlalchemy 對數據庫的高級查詢

對於定義的 ORM 模型,和插入的數據以下:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)
    age = Column(Integer,nullable=False,default=0)
    gender = Column(Enum('male','female','secret'),default='secret')

user1 = User(username='張偉',age=13,gender='male')
user2 = User(username='無名',age=14,gender='secret')
user3 = User(username='翠花',age=19,gender='female')
user4 = User(username='狗剩',age=17,gender='male')
user5 = User(username='李蛋',age=16,gender='female')

session.add_all([user1,user2,user3,user4,user5])
session.commit()

16.1 group_by 查詢

group_by 會根據指定的某個字段進行分組。例如,要根據性別來分組,統計每一個性別的人數,可使用以下代碼實現:

result = session.query(User.gender,func.count(User.id)).group_by(User.gender).all()
# 指定查詢 User.gender,而且以 User.id 爲標識來統計,最後按 User.gander 進行分組
print(result)

# 輸出:[('male', 2), ('female', 2), ('secret', 1)]

實際上轉換成的 SQL 語句以下:

SELECT users.gender AS users_gender, count(users.id) AS count_1 FROM users GROUP BY users.gender

16.2 having 查詢

having 是對查找結果的進一步過濾,和 sql 語句中的 where 相似。例如:查看未成年人的人數,能夠先對年齡進行分組統計,再對分組進行過濾。代碼以下所示:

result = session.query(User.age,func.count(User.id)).group_by(User.age).having(User.age<=18).all()
print(result)
# 輸出:[(13, 1), (14, 1), (16, 1), (17, 1)]

16.3 join 方法

先來段代碼壓壓驚:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)

    def __repr__(self):
        return 'username:%s'%self.username

class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer,primary_key=True,autoincrement=True)
    title = Column(String(50),nullable=False)
    create_time = Column(DateTime,nullable=False,default=datetime.now)
    uid = Column(Integer,ForeignKey('users.id'),nullable=False)

    author = relationship('User',backref=backref('articles'))

    def __repr__(self):
        return 'title:%s\tcreate_time:%s'%(self.title,self.create_time)

user1 = User(username='mayi')
user2 = User(username='youdu')

for i in range(1):
    article = Article(title='title %s'%i)
    article.author = user1
    session.add(article)
session.commit()

for i in range(1,3):
    article = Article(title='title %s'%i)
    article.author = user2
    session.add(article)
session.commit()

# 以上代碼建立了兩個模型:User 和 Article。
# 其中 Article 的 uid 屬性與 User 的 id 屬性創建了外鍵約束,而且兩者相互調用的方法是 User.articles 和 Article.author。
# User 創建了兩個實例 user1 和 user2
# Article 創建了三個實例 title0、title1 和 title2
# 其中,user1 關聯了 titile0,user2 關聯了 title1 和 title2

join 分爲 left join(左外鏈接)right join(右外連接)內連接(等值連接),其中左外連接將A表連接到B表的左邊,右外連接將A表連接到B表的右邊;且老是以左表爲主表,右表爲副表。join 方法其實是兩張表聯合在一塊兒進行查詢,例如:要對全部用戶按發表文章的數量進行進行由大到小的排序,可使用以下代碼實現。

# 原生 SQL 語句查詢以下
select users.username,count(articles.id) from users join articles on users.id=articles.uid group by users.id order by count(articles.id) desc;  

# sqlalchemy 方式查詢以下
result = session.query(User,func.count(Article.id)).join(Article,User.id==Article.uid).group_by(User.id).order_by(func.count(Article.id).desc()).all()
print(result)
# 輸出:[(username:youdu, 2), (username:mayi, 1)]

sqlalchemy 查找時候要注意幾點:

  1. 查找的出來的內容是由 query() 括號中的參數決定,而不是由 join 鏈接的兩張表決定。
  2. 若是 join 的兩張表之間有且只有一個外鍵創建起了關係,那麼在 join 的時候就能夠不寫 User.id==Article.uid
  3. 查詢時內連接用 join 完成;左外鏈接用 outterjoin 來完成;

16.4 別名

16.5 子查詢

即在一個 select 中使用一個 select。子查詢能夠在一次訪問數據庫的時候對查詢結果再進行一次查詢,這樣能夠減小對數據庫的操做。當你的網站訪問量很高的時候,建議使用子查詢;當你的網站訪問量比較少,那麼能夠不考慮這個問題。

例如,有以下代碼:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    username = Column(String(50),nullable=False)
    age = Column(Integer,nullable=False,default=0)
    city = Column(String(20),nullable=False)

user1 = User(username='張偉',age=20,city='廣州')
user2 = User(username='無名',age=21,city='廣州')
user3 = User(username='翠花',age=22,city='廈門')
user4 = User(username='狗剩',age=21,city='長沙')
user5 = User(username='李蛋',age=22,city='廈門')

session.add_all([user1,user2,user3,user4,user5])
session.commit()

# 以上代碼建立了 User 模型而且爲其實例化了五個對象,添加到數據庫中

有一個需求:要查找和某人(如翠花)在同一個城市且年齡同樣的全部人。這個需求有兩種實現:

  1. 先查找出翠花,再根據翠花的信息去查找其餘同城同年齡的人。

    cuihua = session.query(User).filter(User.username == '翠花').first()
     other = session.query(User.username,User.city,User.age).filter_by(city=cuihua.city, age=cuihua.age).all()
     print(other)
  2. 使用子查詢

    SQL 語句查詢:

    mysql> select users.username,users.age,users.city from users,(select * from users where users.username='李蛋') as LD where users.city=LD.city and users.age=LD.age;
     # 其中 () 括起來的是子 select 語句,括號外面的 select 語句是根據括號內的 select 的結果進行 select 操做的。結果以下:
     +----------+-----+------+
     | username | age | city |
     +----------+-----+------+
     | 翠花     |  22 | 廈門 |
     | 李蛋     |  22 | 廈門 |
     +----------+-----+------+
     2 rows in set (0.00 sec)

    sqlalchemy 語句查詢:

    temp = session.query(User.city.label('city'),User.age.label('age')).filter(User.username=='李蛋').subquery()
     result = session.query(User.username,User.city,User.age).filter(User.city==temp.c.city,User.age==temp.c.age).all()
     print(result)
    
     # 其中,temp 就是子查詢獲得的變量,subquery 將該查詢轉換爲子查詢,同時使用 label 爲屬性指定別名;
     # result 是根據子查詢進行查詢的變量,在引用時要用 'xxx.c.xx' 的形式,打印出來的結果以下:
     # [('翠花', '廈門', 22), ('李蛋', '廈門', 22)]

17. Flask 下使用 SQLalchemy

在此以前的全部學習,都是 python 下使用 sqlalchemy 操做數據庫的內容,和 Flask 沒有任何關係。但實際上,Flask 也幫咱們對 sqlalchemy 作了一層封裝,好比將全部以前要從 sqlalchemy 中導入的東西都集成到了一個 SQLAlchemy 類中,而鏈接數據庫時,使用 SQLAlchemyFlask 對象進行初始化便可,以下所示:

db = SQLAlchemy(app)

之後全部在以前 sqlalchemy 中導入的內容,均可以從 db 這個對象中獲取。例如建立 ORM 模型的時候,就不須要以前的 declarative_base 來建立基類了,直接用 db.Model 做爲基類。因此能夠用以下代碼實現:

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    username = db.Column(db.String(50),nullable=False)

    def __repr__(self):
        return 'username:%s'%self.username

class Article(db.Model):
    __tablename__ = 'articles'
    id = db.Column(db.Integer,primary_key=True,autoincrement=True)
    title = db.Column(db.String(50),nullable=False)
    uid = db.Column(db.Integer,db.ForeignKey('users.id'),nullable=False)
    author = db.relationship('User',backref='articles')

    def __repr__(self):
        return 'title:%s\tauthor:%s'%self.title,self.author

在建立模型的時候,能夠省略 __tablename__ 的表名指定動做,但不建議,由於明言勝於暗喻。

映射到數據庫中也可使用以下代碼實現:

db.drop_all()
db.create_all()

17.1 Flask-SQLAlchemy 下的增刪改查

session 的使用方法和以前的同樣。其中:

  1. user = User(username='MYYD')
     article = Article(title='abc')
     user.articles.append(article)
     db.session.add(user)
     db.session.commit()
  2. user = User.query.filter(User.id==2).first()
     db.session.delete(user)
     db.session.commit()
  3. user.query.filter(User.id==1).first()
     user.username = 'MYYD'
     db.session.commit()
  4. 在查詢的時候,若是是針對單張表進行查詢,那麼直接使用 TableName.query.xxx.x 的格式便可。並且在查詢的時候也可使用以前學習的 order_byfiltergroup_by 等方法。以下代碼所示:

    users = User.query.order_by(User.id.desc()).all()

18. Flask-script 命令行操做

Flask-script 的做用是能夠經過命令行的方式去操做 Flask。安裝方式:在虛擬環境下 pip install flask-script

18.1 編寫 flask_script 腳本代碼

  1. 新建一個 manage.py 文件,將代碼寫在該文件中,而不是寫在主 app 文件中。內容以下:

    from flask_script import Manager        # 從 flask_script 導入 Manager 
     from flask_script_demo1 import app      # 從 flask_script_demo1 導入 app 
    
     manage = Manager(app)       # 初始化 app 
    
     @manage.command     # 裝飾器
     def runserver():        # 執行命令的程序寫在這個函數下
         print('服務器跑起來了。')
    
     @manage.command     # 裝飾器
     def stopserver():       # 執行命令的程序寫在這個函數下
         print('服務器關閉了。')
    
     @manager.option('-u','--username',dest='username')  # 還能夠在執行命令的時候向命令傳遞參數
     @manager.option('-e','--email',dest='email')    #還能夠在執行命令的時候向命令傳遞參數
     def addBackendUser(username,email):
         user = BackendUser(username=username,email=email)
         db.session.add(user)
         db.session.commit()
    
     if __name__ == '__main__':
         manage.run()
  2. 命令行調用 manage.py 文件:

    在虛擬環境的命令行下,用 python manage.py command 執行 manage.py 文件下的某段程序,如:python manage.py runserverpython manage.py stopserver 分別會執行 manage.py 文件中的 runserver()stopserver() 方法。

    在定義能夠傳遞參數的命令時要注意,使用的裝飾器是 @manager.option(),以 @manager.option('-u','--username',dest='username') 爲例:-u 是在傳遞參數時要指定的選項縮寫,選項完整的寫法是 --usernamedest='username' 是指該選項後面跟的參數要傳遞給 addBackendUser(username,email) 方法中的 username 參數。

    因此在執行能夠傳遞參數的命令時,應該這麼寫(例):python manage.py -u MYYD -e 90q7w0s7x@qq.com。須要注意的是,有幾個參數就要寫幾個 option 裝飾器。

18.2 從其餘文件中調用自命令

若是有一些關於數據庫的操做,咱們能夠放在一個文件中執行。如 db_script.py 文件:

from flask_script import Manager

# 由於本文件不是做爲主 app 文件,因此不須要寫 if __name__ == '__main__'
# 也不須要在初始化的時候傳入 app 文件

DBManage = Manager()

@DBManage.command
def init():
    print('服務器初始化完成。')

@DBManage.command
def migrate():
    print('數據庫遷移完成。')

這時候要想用上 db_script.py 裏定義的命令,須要在主 manage.py 文件中導入該文件並引用該文件的命令:

from db_scripts import DBManage     # 導入 db_script.py 文件
manage.add_command('db',DBManage)   # 引用該文件

# 以後要是想使用 db_script.py 中的命令,命令行中就要經過 python manage.py db init 來調用
# 其中,db 是 manage.add_command() 中引號內的值,調用子命令的方法就是這種格式

18.3 總結

  1. 若是直接在主 manage.py 文件中寫命令,且該命令不須要傳遞參數,調用時只須要執行 python manage.py command_name 格式的命令便可

  2. 若是直接在主 manage.py 文件中寫命令,且該命令須要傳遞參數,調用時須要執行 python manage.py command_name -[選項] 參數 格式的命令

  3. 若是將一些命令集中在另外一個文件中,那麼就須要輸入一個父命令,好比 python manage.py db init

18.4 例子:兩個文件的完整代碼以下

(python2.7 環境)

1. manage.py

# encoding:utf-8

from flask_script import Manager
from flask_script_demo1 import app
from db_scripts import DBManage
manage = Manager(app)

@manage.command
def runserver():
    print u'服務器跑起來了。'

@manage.command
def stopserver():
    print u'服務器中止了。'

@manager.option('-u','--username',dest='username')
@manager.option('-e','--email',dest='email')
def addBackendUser(username,email):
    user = BackendUser(username=username,email=email)
    db.session.add(user)
    db.session.commit()

manage.add_command('db',DBManage)

if __name__ == '__main__':
    manage.run()


2. db_script.py

# encoding: utf-8
from flask_script import Manager

DBManage = Manager()

@DBManage.command
def init():
    print u'服務器初始化完成。'

@DBManage.command
def migrate():
    print u'數據庫遷移完成。'

19. 分開 Models 和解決循環引用

以前咱們都是將數據庫的模型(類)放在主 app 文件中,可是隨着項目愈來愈大,若是對於加入的新的模型,咱們還放在主 app 文件中,就會使主 app 文件愈來愈大,同時也愈來愈很差管理。因此咱們能夠新建一個專門存放模型的文件。如 models.py 文件用來專門存放模型的文件。

  1. 將本來在主 app 文件中定義的模型(類)移動到 models.py 文件中,可是會提示錯誤,因此咱們在 models.py 文件中導入主 app 文件的 dbfrom models_sep.py import db;同時,由於咱們的主 app 文件確定會操做到 models 文件中的模型,因此要在主 app 文件中導入 models 建立的模型。兩個文件的完整代碼下所示:

    1. # 主 app 文件
    
     from flask import Flask
     from flask_sqlalchemy import SQLAlchemy
     from models import Article
    
     app = Flask(__name__)
     db = SQLAlchemy(app)
     db.create_all()
    
     @app.route('/')
     def index():
         return 'index!'
    
     if __name__ == '__main__':
         app.run()
    
     2. # models.py 文件
    
     from flask_script_demo1 import db
    
     class Article(db.Model):
         __tablename__ = 'articles'
         id = db.Column(db.Integer,primary_key=True,autoincrement=True)
         title = db.Column(db.String(100),nullable=False)
         content = db.Column(db.Text,nullable=False)
  2. 執行以上文件,會報錯。

    報錯提示:ImportError: cannot import name Article,出現此類報錯,先排查路徑和導入的內容是否有錯,若保證沒錯,則極可能是出現循環引用。

    報錯緣由:循環引用,即 models_sep.py 引用了 models.py 中的 Article,而 models.py 又引用了 models_sep.py 中的 db,從而形成循環引用。

  3. 解決循環引用:

    解決方法:將 db 放在另外一個文件 exts.py 中,而後 models_sep.pymodels.py 都從 exts.py 中引用 db 變量,這樣就能夠打破引用的循環。

    三個文件的代碼以下:

    1. # exts.py 文件
    
     from flask_sqlalchemy import SQLAlchemy
     db = SQLAlchemy()
    
     2. # 主 app 文件
    
     from flask import Flask
     from models import Article
     from exts import db
     import config
    
     app = Flask(__name__)
     app.config.from_object(config)
     db.init_app(app)
    
     @app.route('/')
     def index():
         return 'index'
    
     if __name__ == '__main__':
         app.run()
    
     3. # models.py 文件
    
     from exts import db
    
     class Article(db.Model):
         __tablename__ = 'articles'
         id = db.Column(db.Integer,primary_key=True,autoincre)
         title = db.Column(db.String(100),nullable=False)
         content = db.Column(db.Text,nullable=False)
  4. 總結:

    分開 models 的目的是:讓代碼更方便管理。

    解決循環引用的方法:把 db 放在一個單獨文件中如 exts.py ,讓主 app 文件和 models 文件都從 exts.py 中引用。

20. flask-migrate 數據庫遷移

這個時候若是咱們的模型(類)要根據需求添加一個做者字段,這時候咱們須要去修改模型 Article,修改完成咱們須要再映射一遍。可是對於 flask-sqlalchemy 而言,當數據庫中存在了某個模型(類)後,再次映射不會修改該模型的字段,即再次映射不會奏效。

20.1 傳統解決辦法:

在數據庫中刪除該模型對應的表格,再將帶有新字段的模型從新進行映射。

很顯然,這種方式明顯很簡單粗暴,很是不安全,由於在企業中一個數據庫中的表格是含有大量數據的,若是刪除可能會形成重大損失。因此咱們須要一個能夠動態修改模型字段的方法,使用 flask-migrate。先安裝:在虛擬環境下使用命令 pip install flask-migrate 便可。

20.2 使用 migrate 動態修改模型字段

使用 flask-migrate 的最簡單方法是:藉助 flask-script 使用命令行來對 flask-migrate 進行操做。一共有好幾個步驟,分別說明一下:

  1. 新建 manage.py 文件:

    新建 manage.py 文件後:

    1. 導入相應的包並初始化 manager

      from flask_script import Manager
       from migrate_demo import app
       from flask_migrate import Migrate,MigrateCommand
       from exts import db
       manager = Manager(app)
    2. 要使用 flask_migrate 必須綁定 appdb

      migrate = Migrate(app,db)
    3. MigrateCommand 命令添加到 manager 中,實際上就是添加 migrate 子命令到 manager

      manager.add_command('db',MigrateCommand)
    4. manage.py 文件代碼以下:

      from flask_script import Manager
       from migrate_demo import app
       from flask_migrate import Migrate,MigrateCommand
       from exts import db
      
       manager = Manager(app)
      
       # 1. 要使用 flask_migrate 必須綁定 app 和 db
       migrate = Migrate(app,db)
      
       # 2. 把 MigrateCommand 命令添加到 manager 中
       manager.add_command('db',MigrateCommand)
      
       if __name__ == '__main__':
           manager.run()
  2. manage.py 文件中,導入須要映射的模型(類):

    由於在主 app 文件中已經再也不須要對模型進行映射,而對模型的操做是在 manage.py 文件中進行的,包括 flask-migrate 動態映射,因此要導入須要映射的模型。

    from models import Article
  3. 完成以上步驟後,便可到命令行中更新數據庫中的模型了:

    1. python manage.py db init,初始化 flask-migrate 環境,僅在第一次執行的時候使用。
    2. python manage.py db migrate,生成遷移文件
    3. python manage.py db upgrade,將遷移文件映射到數據庫的表格中
    4. 更多命令參考 python manage.py db --help
相關文章
相關標籤/搜索