13、Flask實戰第13天:SQLAlchemy操作MySQL數據庫


安裝MySQL

在MySQL官網下載win版MySQL

雙擊運行

后面根據提示設置密碼然后啟動即可,這里我設置的密碼是:123456

我們可以通過Navicat客戶端工具連接上MySQL

address: 127.0.0.1
port: 3306
username: root
password: 123456
連接信息

 創建一個數據庫heboan,字符集設置為utf8

安裝pymysql和sqlalchemy

#進入cmd窗口,然后進入虛擬環境進行安裝
workon flask-env
pip install pymysql
pip install sqlalchemy

 使用SQLAlchemy連接數據庫

在項目目下創建一個文件learn_sql.py,然后運行此文件

 

ORM介紹

 隨着項目越來越大,采用原生SQL的方式在代碼中會出現大量的SQL語句,那么問題就出現:

  1、SQL語句重復利用率不高,越復雜的SQL語句條件越多,代碼越長;會出現很多相近的SQL語句

  2、很多SQL語句是在業務邏輯中拼出來的,如果數據需要更改,就要去修改這些邏輯,比較容易出錯

  3、寫SQL時容易忽略web安全問題,給未來造成隱患,比如sql注入

ORM,全稱Object Relationship Mapping,中文名叫做對象關系映射,通過ORM我們可以使用類的方式去操作數據庫,而不用再寫原生的SQL語句。通過表映射成類,把行作實例,把字段作屬性。ORM在執行對象操作的時候最終還是會把對應的操作轉換為數據庫原生語句。使用ORM有許多優點:

  1、易用性:使用ORM做數據庫的開發可以有效的減少重復SQL語句的概率,寫出來的模型也更加直觀、清晰

  2、性能損耗小:ORM轉換成底層數據庫操作指令確實會有一些開銷,但從實際情況來看,這種性能損耗不足5%

  3、設計靈活:可以輕松寫出復雜的查詢

  4、可以移植性:SQLAlchemy封裝了底層的數據庫實現,支持多個關系型數據庫引擎,可以輕松的切換數據庫

 

定義ORM模型並將其映射到數據庫中(創建表)

運行之后,查看數據庫,person表已經創建好了

 注意:一旦使用Base.metadata.create_all()將模型映射到數據庫后,即使改變了模型的字段,也不會重新映射了

 

SQLAlchemy對數據庫增刪改查操作 

SQLAlchemy使用ORM對數據庫操作需要構建一個session對象,所有操作都必須通過中國session會話對象來實現

from sqlalchemy.orm import sessionmaker
...

session = sessionmaker(engine)()

說明:

sessionmasker和上面的declarative_base一樣,把engine作為參數實例化出一個對象

  Session = sessionmaker(engine)

但是這樣還不能直接使用Session,我們需要調用Session,我們知道直接實例.()這樣運行直接上會調用__call__()方法

  session = Session()

為了簡介,我們就可以寫成這樣

  session = sessionmaker(engine)()

def add_data():
    p = Person(name='heboan', age=18)
    session.add(p)
    session.commit()

add_data()




#一次添加多條數據
def add_data():
    p1 = Person(name='user1', age=21)
    p2 = Person(name='user2', age=22)
    session.add_all([p1,p2])
    session.commit()

add_data()
def search_data():
    # 查找某個模型表的所有數據
    # all_person = session.query(Person).all()
    # for p in all_person:
    #     print(p)

    # 使用filter_by作條件查詢
    # all_person = session.query(Person).filter_by(name='heboan').all()
    # for p in all_person:
    #     print(p)

    # 使用filter作條件查詢
    # all_person = session.query(Person).filter(Person.name=='heboan').all()
    # for p in all_person:
    #     print(p)
    
    # 使用get方法查找,get方法是根據主鍵ID來查找的,只會返回一條數據或None
    person = session.query(Person).get(1)
    print(person)

search_data()
def update_data():
    # 先找出要改的對象
    person = session.query(Person).first()
    # 直接改屬性
    person.age = 20
    session.commit()

update_data()
def delete_data():
    persons = session.query(Person).filter_by(name='heboan')
    for p in persons:
        session.delete(p)
    session.commit()

delete_data()

 

SQLAlchemy屬性常用數據類型

default: 默認值
nullable: 是否可空
primary_key: 是否為主鍵
unique: 是否唯一
autoincrement: 是否自增長
name: 該屬性再數據庫中的字段映射
onupdate: 當數據更新時會自動使用這個屬性
  比如update_time = Colum(DateTime, onupdate=datetime.now, default=datetime.now)
Colum常用屬性
Integer: 整形
Float: 浮點型,后面只會保留4位小數,會有精度丟失問題,占據32位
Double: 雙精度浮點類型,占據64位,也會存在精度丟失問題
DECIMAL: 定點類型,解決浮點類型精度丟失問題;如果精度要求高,比如金錢,則適合用此類型
Boolean: 傳遞True/False進行
enum: 枚舉類型
Date: 傳遞datetime.date()進去
Datetime: 傳遞datetime.datetme()進去
Time: 傳遞datetime.time()進去   
String: 字符類型,使用時需要指定長度,區別於Text類型
Text: 文本類型,一般可以存儲6w多個字符
LONGTEXT: 長文本類型
  from sqlalchemy.dialects.mysql import LONGTEXT
  因為LONGTEXT只在MySQL數據庫中存在
常用數據類型
...
from sqlalchemy import Column, Integer, String, Enum, Date, Float, DECIMAL, Text, Boolean, DateTime
import datetime
...


class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    gender = Column(Enum('m', 'f'), default='m')
    birthday = Column(Date)
    height = Column(Float)
    wages = Column(DECIMAL(10,4)) #10表示10個阿拉伯數字,4代表4位小數,如100000.1234
    introduction = Column(Text)
    is_delete = Column(Boolean, default=False)
    update_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

Base.metadata.create_all()


session = sessionmaker(engine)()

user = User()
user.username = 'heboan'
user.email = 'love@heboan.com'
user.gender = 'm'
user.birthday = datetime.date(2000, 12, 12)
user.height = 1.72
user.wages = 200000.1234
user.introduction = 'hello, my name is heboan....'

session.add(user)
session.commit()
示例

 

query函數可查詢的數據

為了方便演示,我先建一個Student表,然后寫入10條數據

...
import random

...
class Student(Base):
    __tablename__ = 'student'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    score = Column(Integer, nullable=False)

    def __repr__(self):
        return '<Student-id:{},name:{},score:{}>'.format(self.id,self.name,self.score)

Base.metadata.create_all()
session = sessionmaker(engine)()

for i in range(1, 11):
    student = Student(name='user{}'.format(i), score=random.randint(20,100))
    session.add(student)
    session.commit()
創建測試數據

1、模型對象。指定查找這個模型的所有對象

result = session.query(Student).all()
print(result)

#[<Student-id:1,name:user1,score:98>, <Student-id:2,name:user2,score:42>,...]

2、模型屬性。指定查找某個模型的其中幾個屬性

result = session.query(Student.id, Student.name).first()
print(result)

#(1, 'user1')

3、聚合函數

  func.count 統計行的數量
  func.avg 求平均值
  func.max 求最大值
  func.min 求最小值
  func.sum 求和

...
from sqlalchemy import func
...

result = session.query(func.avg(Student.score)).all()
print(result)

#[(Decimal('55.6000'),)]
示例

 

filter方法常用的過濾條件

#查找id為1的記錄
article = session.query(Article).filter(Article.id == 1).first()
equal
#查找title不是c++的記錄
articles = session.query(Article).filter(Article.title != 'c++').all()
not equal
#查找title以p開頭的記錄
articles = session.query(Article).filter(Article.title.like('p%')).all()
like
#查找標題不是php也不是java的記錄

#方式1:
articles = session.query(Article).filter(Article.title.notin_(['php', 'java'])).all()

#方式2:
articles = session.query(Article).filter(~Article.title.in_(['php', 'java'])).all()
in
#查找detail為空的記錄
articles = session.query(Article).filter(Article.detail == None).all()
is null
#查找detail不為空的記錄
articles = session.query(Article).filter(Article.detail != None).all()
is not null
//查找title是python並且detail是python111的記錄

#方法一:直接使用關鍵字參數
articles = session.query(Article).filter(Article.title=='python', Article.detail=='python111').all()

#方法二,導入and_方法,from sqlalchemy import and_
articles = session.query(Article).filter(and_(Article.title=='python', Article.detail=='python111')).all()
View Code
#查找title是python,或者detail是python111的記錄

導入or_方法, from sqlalchemy import or_
articles = session.query(Article).filter(or_(Article.title=='python', Article.detail=='python111')).all()
or

 

外鍵及其四種約束

在MySQL中,外鍵可以讓表之間的關系更加緊密。而SQLAlchemy同樣支持外鍵。通過ForeignKey類來實現。並且可以指定表的外鍵約束。

from sqlalchemy import ForeignKey

外鍵的約束有以下幾項

  1、RESTRICT : 父表數據被刪除,會阻止刪除。默認是此項

  2、NO ACTION : 在MySQL中,同RESTRICT一樣

  3、CASCADE : 級聯刪除

  4、SET NULL : 父表被刪除,子表數據會設置為NULL(該字段允許為NULL)

...
from sqlalchemy import ForeignKey
...

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


class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    #注意:這里的uid的數據類型必須要和user表中的id數據類型一致
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))

Base.metadata.create_all()
session = sessionmaker(engine)()

user = User(username='heboan')
session.add(user)
session.commit()

article = Article(title='hello world', content='this is a test line', uid=1)
session.add(article)
session.commit()
示例

ondelete設置外鍵約束,沒有設置該屬性則為RESTRICT, 上面我們設置為了RESTRICT

因為我們設置了外鍵約束是RESTRICT,所以現在刪除父表user中id為1 的記錄是會被阻止的

 

ORM層面(代碼)刪除數據,會無視MySQL級別的外鍵約束。直接將對應的數據刪除,然后將從表中的那個外鍵設置為NULL,如果想要避免這種行為,應該將從表中的外鍵的nullable=False

ORM層外鍵和一對多關系

基於上面的情況,就是有兩個表,分別為user 和article。他們兩之間存在一個外鍵關系

需求:根據文章找到用戶信息原始的做法如下

article = session.query(Article).first()
uid = article.uid
user = session.query(User).get(uid)
print(user)

 上面的方法不夠方便,orm可以這樣實現

...
from sqlalchemy.orm import relationship
...

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))
    author = relationship("User")  #添加作者屬性,通過relationship指向user模型表


article = session.query(Article).first()
print(article.author)
orm通過實現relationship外鍵查找

我們也可以通過user 來找到該作者發表的文章,一個作者可能有發表多篇文章,這是一對多關系。同樣的也是relationship實現

...
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    articles = relationship('Article')
View Code
user = session.query(User).get(2)
for article in user.articles:
    print(article.title)

上面我們兩個模型都互相做了relationship實現,這樣比較麻煩,還有一種更簡便的方法

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


class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    content = Column(Text)
    uid = Column(Integer, ForeignKey('user.id', ondelete='RESTRICT'))
    author = relationship("User", backref="articles" ) #這樣user模型表就自動擁有了articles屬性關聯
反向映射

有了上面的關系后,我們添加文章就可以這樣

...
user = session.query(User).get(2)
article1 = Article(title='aaa', content='11aa')
article1.author = user

session.add(article1)
session.commit()
添加文章方法1
...
user = session.query(User).get(2)
article1 = Article(title='bb', content='11bb')
user.articles.append(article1)

session.add(user)
session.commit()
添加文章方法2

一對一關系

 舉個例子,把user表一些不常用查詢的字段放到另外一種表user_extent,這種情況下,這兩張表就是一對一的關系

如果想要將兩個模型映射成一對一的關系,那么應該在父模型中,指定引用的時候,需要傳遞一個‘uselist=False’參數,就是告訴父模型,以后引用這個從模型的時候,不再是一個列表,而是一個對象

from sqlalchemy.orm import backref
...

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

class User_Extent(Base):
    __tablename__ = 'user_extent'
    id = Column(Integer, primary_key=True, autoincrement=True)
    school = Column(String(50))
    uid = Column(ForeignKey('user.id'))
    user = relationship('User', backref=backref('extend', uselist=False))
示例

一對一關系映射后,當我們把一個用戶設置2個學校就會失敗

因此只能一個user只能對應user_extend中的一條記錄

 

多對多關系

場景: 一遍文章可以有多個標簽,一個標簽可以對應多篇文章

運行后,可以發現數據庫成功創建了三張表

我們可以看看創建表的語句

現在我們可以寫入數據

...
article1 = Article(title='Python開發')
article2 = Article(title='Java進階')

tag1 = Tag(name='編程')
tag2 = Tag(name='計算機')

article1.tags.append(tag1)
article1.tags.append(tag2)

article2.tags.append(tag1)
article2.tags.append(tag2)

session.add_all([article1, article2])
session.commit()
寫入數據

...
article = session.query(Article).get(1)
for tag in article.tags:
    print(tag.name)
查詢文章id為1(Python開發)的標簽有哪些
...
tag = session.query(Tag).get(1)
for article in tag.articles:
    print(article.title)
查詢標簽是'編程'的文章有哪些

 

relationship方法中的cascade參數詳解

如果將數據庫的外鍵設置為RESTRICT,那么在ORM層面,刪除了父表中的數據,那么從表中的數據將會NULL。如果不想要這種情況發生,那么應該將這個值的nullable=False。

在SQLAlchemy,只要將一個數據添加到session中,和其他相關聯的數據可以一起存入到數據庫中了。這些是怎么設置的呢?其實是通過relationship的時候,有一個關鍵字參數cascade可以這是這些屬性:

  1、save-update:默認選項。在添加一條數據的時候,會把其他和其他關聯的數據都添加到數據中

  2、delete: 表示當刪除某一個模型的數據的時候,是否也刪掉使用relationship和他關聯的數據

  3、delete-orphan: 表示當對一個ORM對象解除了父表中的關聯對象的時候,自己便會被刪除掉。當然如果父表中的數據被刪除,自己也會被刪除。這個選項只能用在一對多上,不能用在多對多以及多對以上。並且還需要再子模型中的relationship中,增加一個single_parent=True的參數

  4、merge:默認選項。當在使用session.merge合並一個對象的時候,會將使用了relationship相關聯的對象也進行merge操作

  5、expunge: 移除操作的時候,會將相關聯的對象也進行移除。這個操作只是從session中移除,並不會真正的從數據中刪除

  6、all: 是對save-update, merge, refresh-expire, expunge, delete幾種的縮寫

 

排序

order_by:根據表中的某個字段進行排序,如果前面加了一個"-",代表的是降序

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

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()
表結構

插入兩條測試數據

現在按照文章發表時間排序

article=session.query(Article).order_by(Article.create_time).all()
print(article)

#結果:[<title:title1,create_time:2018-07-08 10:33:17>, <title:title2,create_time:2018-07-08 10:34:00>]
升序
article=session.query(Article).order_by(Article.create_time.desc()).all()
print(article)

#結果:[<title:title2,create_time:2018-07-08 10:34:00>, <title:title1,create_time:2018-07-08 10:33:17>]
降序

方法二:

#升序
article=session.query(Article).order_by('create_time').all()

#降序
article=session.query(Article).order_by('-create_time').all()
示例

__mapper_args__ : 定表模型的時候確定默認的排序規則

class Article(Base):
    __tablename__ = 'article'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    create_time = Column(DateTime, nullable=False, default=datetime.now)
    __mapper_args__ = {
        "order_by": create_time.desc()   #根據發表時間降序
    }

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()

...


article=session.query(Article).all()
print(article)

#結果
[<title:title2,create_time:2018-07-08 10:34:00>, <title:title1,create_time:2018-07-08 10:33:17>]
定義默認排序規則

 

limit、offset以及切片操作

...

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

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()



Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()

#寫入100條測試數據
for x in range(1,101):
    time.sleep(2)
    title = "title {}".format(x)
    article = Article(title=title)
    session.add(article)
    session.commit()
創建表並寫入測試數據

limit:可以限制每次查詢的時候只查詢幾條數據

#查詢出全部的數據
articles = session.query(Article).all()
print(articles)


#通過limit查出10條
articles = session.query(Article).limit(10).all()
print(articles)
limit
articles = session.query(Article).order_by(Article.create_time.desc()).limit(10).all()
print(articles)
查出最近發表的10篇文章

offset: 可以限制查找數據的時候過濾前面多少條

#從第5條后面開始查詢出10條數據
articles = session.query(Article).offset(5).limit(10).all()
print(articles)


#結果
[<title:title 6,create_time:2018-07-08 12:32:03>,...<title:title 15,create_time:2018-07-08 12:32:21>]
offset

切片: 可以對Query對象使用切片操作,來獲取想要的數據

#切片有兩種方式:

#需求:找出最近發表的10篇文章

#1、是slice(start,end)
articles = session.query(Article).order_by(Article.create_time.desc()).slice(0, 10).all()
print(articles)

#2、和列表一樣的操作
articles = session.query(Article).order_by(Article.create_time.desc())[0:10]
print(articles)
切片

 

數據查詢懶加載技術

 在一對多,或者多對多的時候,如果想要獲取多的這一部分的數據的時候,往往能通過一個屬性就可以獲取全部了。比如有一個作者,想要獲取這個作者的所有文章,那么可以通過user.articles就可以。但有時候我們不想獲取所有的數據,比如只想獲取這個作者今天發表的文章,那這時候我們就可以給relationship傳遞一個lazy='dynamic',以后通過user.articles獲取到的就不是一個列表,而是一個AppendQuery對象了。這樣就可以對這個對象再進行一層過濾和排序等操作

lazy可用的選項:

1、'select':這個是默認選選項,比如‘user.articles’的例子,如果你沒有訪問‘user.articles’這個屬性,那么sqlalchemy就不會從數據庫中查找文章。一旦你訪問了這個屬性,那么sqlalchemy就會立馬從數據庫中查找所有的文章。這也是懶加載。

2、‘dynamic’:就是在訪問‘user.articles’的時候返回的不是一個列表,而是一個AppendQuery對象

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

    def __str__(self):
        return self.username

    def __repr__(self):
        return self.__str__()


class Article(Base):
    __tablename__ = 'article'
    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('user.id'), nullable=False)

    author = relationship(User, backref=backref('articles', lazy='dynamic'))   #lazy='dynamic'

    def __str__(self):
        return "<title:{},create_time:{}>".format(self.title, self.create_time)

    def __repr__(self):
        return self.__str__()



Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()

#寫入測試數據
user = User(username='heboan')
session.add(user)
session.commit()

user = session.query(User).first()

for x in range(1,101):
    time.sleep(2)
    title = "title {}".format(x)
    article = Article(title=title)
    article.author = user
    session.add(article)
    session.commit()
dynamic

上面配置了lazy='dynamic',並寫入了測試數據,我們可以看看‘user.articles’類型為AppendQuery

...
user = session.query(User).first()
print(type(user.articles))


#結果
<class 'sqlalchemy.orm.dynamic.AppenderQuery'>
示例

既然user.articles為AppendQuery類型,那么它就有query擁有的屬性,比如(filter、order_by等等),還可以append數據,如下

...
user = session.query(User).first()
# filter
print(user.articles.filter(Article.create_time == '2018-07-08 16:55:05').all())
#結果: [<title:title 2,create_time:2018-07-08 16:55:05>]

#append
article = Article(title='python is simple')
user.articles.append(article)
session.commit()
示例

 

group_by和having子句

...

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

    def __str__(self):
        return self.username

    def __repr__(self):
        return self.__str__()


# Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()


user1 = User(username='王五', age=17, gender='male')
user2 = User(username='趙四', age=17, gender='male')
user3 = User(username='張三', age=18, gender='female')
user4 = User(username='張偉', age=19, gender='female')
user5 = User(username='劉海', age=20, gender='female')

session.add_all([user1, user2, user3, user4, user5])
session.commit()
創建測試表和數據

group_by

根據某個字段進行分組。比如想要根據年齡進行分組,來統計每個分組分別有多少人

from sqlalchemy import func
...

#每個年齡的人數
result = session.query(User.age, func.count(User.id)).group_by(User.age).all()
print(result)    


#結果
[(17, 2), (18, 1), (19, 1), (20, 1)]
每個年齡的人數

查看sql語句

having

having是對查找結果進一步過濾。比如只想要看成年人的數量,那么可以首先對年齡進行分組統計人數,然后再對分組進行having過濾

#每個年齡的人數
result = session.query(User.age, func.count(User.id)).group_by(User.age).having(User.age >= 18).all()
print(result)

#結果
[(18, 1), (19, 1), (20, 1)]
按年齡分組並查看成年人人數

 

join實現復雜查詢

是使用SQLAlemy 的join功能之前,我們先了解原生的sql語句中的left join, right join, inner join用法

#創建表a
CREATE TABLE a(
aID int(1) AUTO_INCREMENT PRIMARY KEY,
aNUM char(20)
);

#創建表b
CREATE TABLE b(
bID int(1) AUTO_INCREMENT PRIMARY KEY,
bNUM char(20)
);

#插入數據到表a
INSERT INTO a VALUES
( 1, 'a20050111' ),
( 2, 'a20050112' ),
( 3, 'a20050113' ),
( 4, 'a20050114' ),
( 5, 'a20050115' );

#插入數據到表b
INSERT INTO b VALUES
( 1, ' 2006032401'),
( 2, '2006032402' ),
( 3, '2006032403' ),
( 4, '2006032404' ),
( 8, '2006032408' );
創建a,b兩張表

表a記錄如下

表b記錄如下

left join(左聯接)

結果說明:

left join 是以a表的記錄為基礎,a可以看成左表,B可以看成右表,left join是以左表為准的,換句話說,左表(a)的記錄將會全部表示出來,而右表(b)只會顯示復合搜索條件的記錄,B表記錄不足的地方均為NULL.

right join(右聯接)

結果說明:

和left join的結果剛好相反,這次是以右表(b)為基礎的,a表不足的地方用null填充

inner join(內聯接)

結果說明

很明顯,這里只顯示出了 a.aID = b.bID的記錄.這說明inner join並不以誰為基礎,它只顯示符合條件的記錄. 

SQLAlcemy使用join

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

    def __str__(self):
        return self.username

    def __repr__(self):
        return self.__str__()

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

    def __str__(self):
        return self.title

    def __repr__(self):
        return self.__str__()


# Base.metadata.drop_all()
# Base.metadata.create_all()
session = sessionmaker(engine)()

user1 = User(username='jack')
user2 = User(username='slina')


#用戶jack有3篇文章
for x in range(3):
    article = Article(title='jack title {}'.format(x))
    article.author = user1
    session.add(article)
    session.commit()

#用戶slina有5篇文章
for x in range(5):
    article = Article(title='slina title {}'.format(x))
    article.author = user2
    session.add(article)
    session.commit()
創建表及數據

 需求:查詢出每個用戶發表的文章數,並按倒序排序

我們先用原生sql寫一遍:

1、先把兩張表內連接查詢

2、使用user.id進行分組統計出每個用戶的文章數

3、最后把查詢出來的結果依據文章數進行倒序排序

我們再來用sqlalchemy寫出來

看下他的SQL語句是怎么樣的

 注意2點:

  在sqlalchemy中,使用join來完成內聯接。在寫join的時候,如果不寫join條件(User.id==Article=uid),那么默認使用外鍵作為條件連接

  query查找出什么值,不會取決於join后面的東西,而是取決於query方法中傳了什么參數。就跟原生sql 中的select后面那個一樣

 

subquery實現復雜查詢

子查詢可以讓多個查詢變成一個查詢,只要查找一次數據庫,性能相對來講更高效一點

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

    def __str__(self):
        return self.username

    def __repr__(self):
        return self.__str__()


# Base.metadata.drop_all()
Base.metadata.create_all()
session = sessionmaker(engine)()

user1 = User(username='李A', city="長沙", age=18)
user2 = User(username='王B', city="長沙", age=18)
user3 = User(username='趙C', city="北京", age=18)
user4 = User(username='張D', city="長沙", age=20)

session.add_all([user1, user2, user3, user4])
session.commit()
創建測試表及數據

需求:尋找和李A這個人在同一個城市,並且是同年齡的人

我們可能會這樣查詢:

上面的方法可以實現需求,但是卻需要查詢兩次數據庫,這樣必定會降低數據庫查詢性能,我們可以使用子查詢的方式來實現需求:

原生sql子查詢如下

使用sqlalchemy實現

在子查詢中,將以后需要用到的字段通過 label方法取個別名

在父查詢中,如果想要使用子查詢的字段,那么可以通過子查詢的返回值的 c屬性難道

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM