1三、Flask實戰第13天:SQLAlchemy操做MySQL數據庫

安裝MySQL

在MySQL官網下載win版MySQLphp

雙擊運行java

後面根據提示設置密碼而後啓動便可,這裏我設置的密碼是:123456python

咱們能夠經過Navicat客戶端工具鏈接上MySQLmysql

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

 建立一個數據庫heboan,字符集設置爲utf8c++

安裝pymysql和sqlalchemy

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

 使用SQLAlchemy鏈接數據庫web

在項目目下建立一個文件learn_sql.py,而後運行此文件sql

 

ORM介紹

 隨着項目愈來愈大,採用原生SQL的方式在代碼中會出現大量的SQL語句,那麼問題就出現:數據庫

  一、SQL語句重複利用率不高,越複雜的SQL語句條件越多,代碼越長;會出現不少相近的SQL語句編程

  二、不少SQL語句是在業務邏輯中拼出來的,若是數據須要更改,就要去修改這些邏輯,比較容易出錯flask

  三、寫SQL時容易忽略web安全問題,給將來形成隱患,好比sql注入

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

  一、易用性:使用ORM作數據庫的開發能夠有效的減小重複SQL語句的機率,寫出來的模型也更加直觀、清晰

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

  三、設計靈活:能夠輕鬆寫出複雜的查詢

  四、能夠移植性: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()
建立測試數據

一、模型對象。指定查找這個模型的全部對象

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

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

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

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

#(1, 'user1')

三、聚合函數

  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

外鍵的約束有如下幾項

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

  二、NO ACTION : 在MySQL中,同RESTRICT同樣

  三、CASCADE : 級聯刪除

  四、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能夠這是這些屬性:

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

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

  三、delete-orphan: 表示當對一個ORM對象解除了父表中的關聯對象的時候,本身便會被刪除掉。固然若是父表中的數據被刪除,本身也會被刪除。這個選項只能用在一對多上,不能用在多對多以及多對以上。而且還須要再子模型中的relationship中,增長一個single_parent=True的參數

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

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

  六、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篇文章

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

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

 

數據查詢懶加載技術

 在一對多,或者多對多的時候,若是想要獲取多的這一部分的數據的時候,每每能經過一個屬性就能夠獲取所有了。好比有一個做者,想要獲取這個做者的全部文章,那麼能夠經過user.articles就能夠。但有時候咱們不想獲取全部的數據,好比只想獲取這個做者今天發表的文章,那這時候咱們就能夠給relationship傳遞一個lazy='dynamic',之後經過user.articles獲取到的就不是一個列表,而是一個AppendQuery對象了。這樣就能夠對這個對象再進行一層過濾和排序等操做

lazy可用的選項:

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

二、‘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寫一遍:

一、先把兩張表內鏈接查詢

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

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

咱們再來用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屬性難道

相關文章
相關標籤/搜索