Flask-SQLAlchemy SQLAlchemy外键关联使用及其详细说明       Flask-SQLAlchemy外键多对多关系         article_tag 表:   彻底搞懂 SQLAlchemy中的 backref和back_populates Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系 一对多 一对一关系 多对多关系 参考 Flask-SQLAlchemy 学习总结 使用flask-SQLAlchemy错误(一) https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9  https://www.cnblogs.com/liangmingshen/p/9769975.html https://www.jianshu.com/p/92890a4ec0cb https://blog.csdn.net/qq_25730711/article/det

SQLAlchemy数据库增删改查 https://www.jianshu.com/p/b7704b6cb2ee
ORM是需要了解的:Object-Relational Mapping,把关系数据库的表结构映射到对象上,在Python中,ORM框架是SQLAlchemy。

这里用简单的两张表来记录SQLAlchemy数据库关联的使用。

首先创建在User模型创建了一张表,表名为“user”

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

然后在模型Article中创建了一张名为“article”的表

class Article(db.Model):
    __tablename__ = 'article'
    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)
    author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    author = db.relationship('User', backref=db.backref('articles'))

这里将User中的id关联到Article中的author_id,也就是author_id就是User中的id。
关联的时候数据类型要保持一致,如db.Integer。可以通过Navicat for MySQL中的ER图标来查看。

author_id = db.Column(db.Integer, db.ForeignKey('user.id'))

这里我在User表中添加了3条数据:

 

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687
 

 

在Article中添加了2条数据,这两条数据是绑定在刘备下的

 

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687
 

添加的代码如下:

    username = request.args.get("username")
    user = User(username=username)
    db.session.add(user)
    db.session.commit()
    title = request.args.get("title")
    content = request.args.get("content")
    aitlcle = Article(title=title, content=content, author_id=1)
    db.session.add(aitlcle)
    db.session.commit()

准备工作完成了,外键肯定是关联成功了,可以通过运行代码来查看:

查看title为“如何收复汉室?”的这个作者是谁

    article = Article.query.filter(Article.title == '如何收复汉室?').first()
    author_id = article.author_id
    user = User.query.filter(User.id == author_id).first()

下面这种写法更简单,在Article中如此:

author = db.relationship('User', backref=db.backref('articles')):

第一个参数为模型User的名字(class User),这个是正向引用, Article引用User
第二个参数为反向引用,User引用Article

# 查找刘备还写过哪些文章    正向引用  Article引用User
    article = Article.query.filter(Article.title == '如何收复汉室?').first()
    print('username:%s' % article.author.username)

上面通过正向引用,也就是Article引用User来得到title为“如何收复汉室?”的这个作者是谁,打印结果为:

username:刘备

实现了正向引用,来看看反向引用,比如刘备还发表了哪些文章,即User引用Article:

    user = User.query.filter(User.username == '刘备').first()
    articles = user.articles    #此处直接反向引用得到所有的文章
    for article in articles:
        print(article.title)

打印结果:

如何收复汉室?
等纸

Flask-SQLAlchemy外键多对多关系 https://www.jianshu.com/p/5282a7525e52

Flask-SQLAlchemy外键多对多关系

0.3692019.01.08 17:26:04字数 437阅读 977

SQLAlchemy外键关联一对一 https://www.jianshu.com/p/02c1a33ca1e9

class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    tags = db.relationship('Tag', secondary=article_tag, backref=db.backref('articles'))

tags = db.relationship('Tag', secondary=article_tag, backref=db.backref('articles')):
Article这个模型添加一个tags属性,可以访问这篇文章的标签的数据,像访问普通模型一样。
backref是定义反向引用,可以通过Tag.articles`访问这个标签所关联的所有文章。
secondary=article_tag这句话将article_id和tag_id关联起来,没有这个的话article_tag Article Tag这个三个是独立的

class Tag(db.Model):
    __tablename__ = 'tag'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(100), nullable=False)

多对多的关系,要通过一个中间表进行关联article_tag就是中间表。
中间表,不能通过class的方式实现,只能通过“ db.Table”的方式进行实现。

article_tag = db.Table('article_tag',
                       db.Column('article_id', db.Integer, db.ForeignKey('article.id'), primary_key=True),
                       db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True)
                       )

下面是这三张表的关系,可以看到article_tag作为一个中间表,被tag和article引用。

 

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687

接下来再tag表和article中添加数据:

@app.route('/addArticleAddTag')
def addArticleAddTag():
    article1 = Article(title='如何实现经济稳定繁荣发展')
    article2 = Article(title='论增强国力的重要性')

    tag1 = Tag(name='经济')
    tag2 = Tag(name='民生')

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

    article2.tags.append(tag1)

    db.session.add(article1)
    db.session.add(article2)

    db.session.add(tag1)
    db.session.add(tag2)

    db.session.commit()
    return 'hello'

tag表:

 

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687

 

article表:

 

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687


article_tag 表:

 
Flask-SQLAlchemy
SQLAlchemy外键关联使用及其详细说明
 
 
 
Flask-SQLAlchemy外键多对多关系
 
 
 
 
 article_tag 表:
 
彻底搞懂 SQLAlchemy中的 backref和back_populates
Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系
一对多
一对一关系
多对多关系
参考
Flask-SQLAlchemy 学习总结
使用flask-SQLAlchemy错误(一)
https://www.jianshu.com/p/5282a7525e52https://www.jianshu.com/p/02c1a33ca1e9 
https://www.cnblogs.com/liangmingshen/p/9769975.html
https://www.jianshu.com/p/92890a4ec0cb
https://blog.csdn.net/qq_25730711/article/details/53690687

 

可以看到article_id为1的文章有两个标签(经济,民生),文章article_id为2的文章只有一个标签(经济),说明已经成功的关联了。

@app.route('/queryData')
def queryData():
    # 查询指定的文章有几个标签
    article1 = Article.query.filter(Article.title == '如何实现经济稳定繁荣发展').first()
    tags = article1.tags
    for tag in tags:
        print(tag.name)

    # 查询指定的标签和哪些文章有关联
    t = Tag.query.filter(Tag.name == '经济').first()
    articles = t.articles  # 此处直接反向引用得到所有的文章
    for article in articles:
        print(article.title)
    return 'Hello World!'

通过正向引用,得到指定文章下有几个标签,这里查询标题为“如何实现经济稳定繁荣发展”tag.name的输出结果为:
民生,经济
通过反向引用,得到指定标签下有哪些文章关联,这里查询标签为“经济”所关联的文章,article_title的输出结果为:
如何实现经济稳定繁荣发展,论增强国力的重要性

通过两次查询,可以看到和我们最开始所说的是一致的。

back_populates

 

教程源码截取:

 
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", backref="user")


class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))
 

简单来说, relationship函数是sqlalchemy对关系之间提供的一种便利的调用方式, backref参数则对关系提供反向引用的声明。
假如没有relationship,我们只能像下面这样调用关系数据:

#给定参数User.name,获取该useraddresses
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    addresses = session.query(Address).filter_by(user_id=user.id).all()
    return addresses
 如果在User中使用relationship定义addresses属性的话,
addresses = relationship('Address')
 则我们可以直接在User对象中通过addresses属性获得指定用户的所有地址。
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    return user.addresses
 注意,在上面的addresses属性中我们并没有定义backref属性,
所以我们可以通过User对象获取所拥有的地址,但是不能通过Address对象获取到所属的用户.
 
>>> u = User()
>>> u.addresses
[]
>>> a = Address()
>>> a.user
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'Address' object has no attribute 'user'
 
 但是当我们有从Address对象获取所属用户的需求时,backref参数就派上用场了。
addresses = relationship('Address', backref='user')
>>> a = Address()
>>> a.user

大致原理应该就是:

sqlalchemy在运行时对Address对象动态的设置了一个指向所属User对象的属性,

这样就能在实际开发中使逻辑关系更加清晰,代码更加简洁了。

一言以蔽之: 

backref用于在关系另一端的类中快捷地创建一个指向当前类对象的属性。

 补充:

db.backref()是你需要对放置 backref的那一边的参数,

(在上例中为 Address类的 .user属性)指定参数时, 使用 backref()函数代替字符串, 常见的有 lazy='dynamic'(禁止自动查询, 用于添加过滤器)。

 backref用于在关系另一端的类中快捷地创建一个指向当前类对象的属性, 而当需要对那个属性指定参数时使用 db.backref()。

 

Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系

一对多

  1. 创建两个模型
class Person(db.Model):
    __tablename__ = 'person'
    name = db.Column(db.String(20), primary_key=True)
    age = db.Column(db.Integer)
    birth = db.Column(db.Date)
    phone = db.Column(db.String(11), unique=True)

    # 使用关系函数定义关系属性
    cars = db.relationship('Car')

    def __repr__(self):
        return '姓名:{name} 年龄:{age} 生日:{birth} 电话:{phone}'.format(name=self.name, age=self.age, birth=self.birth,
                                                                 phone=self.phone)


class Car(db.Model):
    name = db.Column(db.String(10), primary_key=True)
    price = db.Column(db.Float)
    # 定义外键(表明.字段名)
    course_phone = db.Column(db.String(11), db.ForeignKey('person.phone'))

    def __repr__(self):
        return '汽车类型:{name} 总价:{price}'.format(name=self.name, price=self.price)
  1. 通过设置外键建立关系
@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00, course_phone='17777777777')
    car2 = Car(name='吉利*舰', price=43000.00, course_phone='17777777777')
    db.session.add(person)
    db.session.add(car1)
    db.session.add(car2)
    db.session.commit()
    click.echo('insert')

或者通过关系属性cars调用append建立关系

@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00)
    car2 = Car(name='吉利*舰', price=43000.00)
    db.session.add(person)
    person.cars.append(car1)
    person.cars.append(car2)
    db.session.commit()
    click.echo('insert')

通过remove解绑关系

person.cars.remove(car1)
db.session.commit()
  1. 查询
@app.cli.command()
def querypc():
    person = Person.query.first()
    click.echo(person.cars)

查询结果如下:

[汽车类型:五菱宏光 总价:55000.0, 汽车类型:吉利*舰 总价:43000.0]

建立双向关系

  1. 创建两个模型
class Person(db.Model):
    __tablename__ = 'person'
    name = db.Column(db.String(20), primary_key=True)
    age = db.Column(db.Integer)
    birth = db.Column(db.Date)
    phone = db.Column(db.String(11), unique=True)

    # 使用关系函数定义关系属性
    cars = db.relationship('Car', back_populates='person')

    def __repr__(self):
        return '姓名:{name} 年龄:{age} 生日:{birth} 电话:{phone}'.format(name=self.name, age=self.age, birth=self.birth,
                                                                 phone=self.phone)


class Car(db.Model):
    name = db.Column(db.String(10), primary_key=True)
    price = db.Column(db.Float)
    # 定义外键(表明.字段名)
    course_phone = db.Column(db.String(11), db.ForeignKey('person.phone'))

    person = db.relationship('Person', back_populates='cars')

    def __repr__(self):
        return '汽车类型:{name} 总价:{price}'.format(name=self.name, price=self.price)
  1. 建立关系
@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00)
    car2 = Car(name='吉利*舰', price=43000.00)
    db.session.add(person)
    person.cars.append(car1)
    person.cars.append(car2)
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def queryc():
    car = Car.query.first()
    click.echo('{person} {car} '.format(person=car.person, car=car))

查询结果如下:

姓名:老赵 年龄:27 生日:2018-10-24 电话:17777777777 汽车类型:五菱宏光 总价:55000.0

一对一关系

  1. 创建两个模型,注意:创建一对一关系是通过将uselist设为False
class Husband(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    age = db.Column(db.Integer)

    wife = db.relationship('Wife', uselist=False)

    def __repr__(self):
        return '老公:{name} 年齡:{age}'.format(name=self.name, age=self.age)


class Wife(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    age = db.Column(db.Integer)
    husband_id = db.Column(db.Integer, db.ForeignKey('husband.id'))

    husband = db.relationship('Husband')

    def __repr__(self):
        return '老婆:{name} 年齡:{age}'.format(name=self.name, age=self.age)
  1. 建立关系,注意:一对一关系不能使用append,因为是单个记录,所以使用=
@app.cli.command()
def inserthw():
    husband = Husband(name='老王', age=24)
    wife = Wife(name='小红', age=18)
    db.session.add(husband)
    husband.wife = wife
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def queryhw():
    husband = Husband.query.first()
    click.echo('{husband} {wife}'.format(husband=husband, wife=husband.wife))

查询结果如下:

老公:老王 年齡:24 老婆:小红 年齡:18

多对多关系

  1. 建立存储多对多模型的外键对应关系的关联表
association_table = db.Table('association', db.Column('customer_id', db.Integer, db.ForeignKey('customer.id')),
                             db.Column('product_id', db.Integer, db.ForeignKey('product.id')))
  1. 建立两个模型,secondary设为关联表的名称,具体可查看relationship
class Customer(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    work = db.Column(db.String(20))
    products = db.relationship('Product', secondary=association_table, back_populates='customers')

    def __repr__(self):
        return '姓名:{name} 公司:{work}'.format(name=self.name, work=self.work)


class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    price = db.Column(db.Float)
    customers = db.relationship('Customer', secondary=association_table, back_populates='products')

    def __repr__(self):
        return '产品类型:{name} 单价:{price}'.format(name=self.name, price=self.price)
  1. 建立关系
@app.cli.command()
def insertcp():
    customer1 = Customer(name='程老板', work='大兴有限公司')
    customer2 = Customer(name='李老板', work='弘成科技')
    customer3 = Customer(name='司马老板', work='小马加油有限公司')
    product1 = Product(name='丝绸', price=35.12)
    product2 = Product(name='铝合金', price=54.45)
    product3 = Product(name='盐', price=3.00)
    db.session.add(customer1)
    customer1.products.append(product1)
    customer1.products.append(product2)
    customer2.products.append(product2)
    customer3.products.append(product1)
    customer3.products.append(product3)
    product1.customers.append(customer1)
    product1.customers.append(customer3)
    product2.customers.append(customer2)
    product2.customers.append(customer1)
    product3.customers.append(customer3)
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def querycp():
    customer = Customer.query.first()
    click.echo('{customer}  购买了  {products}'.format(customer=customer, products=customer.products))

查询结果:

姓名:程老板 公司:大兴有限公司  购买了  [产品类型:铝合金 单价:54.45, 产品类型:丝绸 单价:35.12]

参考

 

初始化和配置

ORM(Object Relational Mapper) 对象关系映射。指将面对对象得方法映射到数据库中的关系对象中。
Flask-SQLAlchemy是一个Flask扩展,能够支持多种数据库后台,我们可以不需要关心SQL的处理细节,操作数据库,一个基本关系对应一个类,而一个实体对应类的实例对象,通过调用方法操作数据库。Flask-SQLAlchemy有很完善的文档。

Flask-SQLAlchemy是通过URL指定数据库的连接信息的。
初始化的两种方法如下(以连接Mysql数据库为例):

from flask_sqlalchemy import SQLAlchemy
from flask import FLask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 
    "mysql://root:12345@localhost/test"
db = SQLAlchemy(app)

或者

from flask_sqlalchemy import SQLAlchemy
from flask import FLask
db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    return app

两者的区别在于:第一种不需要启动flask的app_context;但是第二种方法则需要,因为可能会创建多个Flask应用,但是我的理解是一般地开发时,Flask实例是延迟创建的,因为在运行时难以修改配置信息,这种方法符合这种情况。
Flask-SQLAlchemy的则需要在Flask.config中声明。更多详细信息需要查配置。例如配置信息中指出SQLAlchemy是可以绑定多个数据库引擎。再例如:在新浪SAE云平台开发个人博客时遇到gone away这种问题就需要添加SQLALCHEMY_POOL_RECYCLE信息,新浪开发者文档中有说明。


SQLALchemy处理 对象->关系

SQLAlchemy是如何处理对象到关系的?实例来自于数据库系统概论内容。

简单实例

创建学生students表

class Student(db.Model):
    __tablename__ = 'students' #指定表名
    sno = db.Column(db.String(10), primary_key=True)
    sname = db.Column(db.String(10))
    sage = db.Column(db.Integer)

API文档说明创建对象需要继承db.Model类关联数据表项,db.Model类继承Query类提供有数据查询方法;__tablename__指定数据库的表名,在Flask-SQLAlchemy中是可省的。Column指定表字段。

SQLAlchemy支持字段类型有:

类型名 python类型 说明
Integer int 普通整数,32位
Float float 浮点数
String str 变长字符串
Text str 变长字符串,对较长字符串做了优化
Boolean bool 布尔值
PickleType 任何python对象 自动使用Pickle序列化

来源于Simple ExampleFlask Web开发有更详细的内容。
其余的参数指定属性的配置选项,常用的配置选项如下:

选项名 说明
primarykey 如果设为True,表示主键
unique 如果设为True,这列不重复
index 如果设为True,创建索引,提升查询效率
nullable 如果设为True,允许空值
default 为这列定义默认值

如使用default默认time属性如下:

time = db.Column(db.Date, default=datetime.utcnow)

说明default可以接受lambda表达式。

一对多

按创建单张表的方法,创建学院Deptment表

class Deptment(db.Model):
    __tablename__ = 'deptments'
    dno = db.Column(db.Integer, primary_key=True)
    dname = Sname = db.Column(db.String(10),index=True)

学院和学生是一对多的关系。Flask-SQLAlchemy是通过db.relationship()解决一对多的关系。在Dept中添加属性,代码如下:

class Deptment(db.Model):
    ...
    students = db.relationship('Student', backref='dept')
    
    
class Student(db.Model):
    ...
    dept_no = db.Column(db.Integer, db.ForeignKey('deptments.dno'))

表的外键由db.ForeignKey指定,传入的参数是表的字段。db.relations它声明的属性不作为表字段,第一个参数是关联类的名字,backref是一个反向身份的代理,相当于在Student类中添加了dept的属性。例如,有Deptment实例dept和Student实例stu。dept.students.count()将会返回学院学生人数;stu.dept.first()将会返回学生的学院信息的Deptment类实例。一般来讲db.relationship()会放在这一边。

多对多

多对多的关系可以分解成一对多关系,例如:学生选课,学生与课程之间的关系:

sc = db.Table('sc',
    db.Column('sno', db.String(10), db.ForeignKey('students.sno'))
    db.Column('cno',db.String(10), db.ForeignKey('courses.cno'))
    )
    
Class Course(db.Model):
    __tablename__ = 'courses'
    cno = db.Column(db.String(10), primary_key=True)
    cname = db.Column(db.String(10), index=True)
    students = db.relationship('Student',
         secondary=sc,
         backref=db.backref('course',lazy='dynamic'),
         lazy='dynamic'
         )

sc表由db.Table声明,我们不需要关心这张表,因为这张表将会由SQLAlchemy接管,它唯一的作用是作为students表和courses表关联表,所以必须在db.relationship()中指出sencondary关联表参数。lazy是指查询时的惰性求值的方式,这里有详细的参数说明,而db.backref是声明反向身份代理,其中的lazy参数是指明反向查询的惰性求值方式,SQLAlchemy鼓励这种方式声明多对多的关系。

但是如果关联表中有自定义的字段,如sc表中添加成绩字段则需要更改表声明方式,将sc更改为继承db.Model的对象并设置sc:courses = 1:n 和sc:student = 1:n的关系。


SQLALchemy处理 关系->对象

Flask-SQLAlchemy查询中有详细的说明。创建关系后该如何查询到对象?

SQLAlchemy有查询过滤器如下:

过滤器 说明
filter() 把过滤器添加到原查询,返回新查询
filter_by() 把等值过滤器添加到原查询,返回新查询
limit() 使用指定值限制原查询返回的结果数量,返回新查询
offset() 偏移原查询返回的结果,返回新查询
order_by() 排序返回结果,返回新查询
groupby() 原查询分组,返回新查询

这些过滤器返回的结果都是一个新查询,我的理解是这些查询其实是生成的SQL语句,lazy的惰性求值方式也体现在查询上,而这些语句不能生成需要查询的对象,需要调用其他的方法生成对象。

SQL查询执行函数:

方法 说明
all() 以列表形式返回结果
first() 返回第一个结果,如果没有返回None
first_or_404() 返回第一个结果,如果没有抛出404异常
get() 返回主键对应记录,没有则返回None
get_or_404() 返回主键对应记录,如果没有抛出404异常
count() 返回查询结果数量
paginate() 返回paginate对象,此对象用于分页

使用flask-SQLAlchemy错误(一)

 

错误信息如下:

flask_sqlalchemy\__init__.py:800: UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.
  warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.')

错误信息提示的很明确,修改 SQLALCHEMY_TRACK_MODIFICATIONS 为True以移除这个警告。

尝试进行了如下修改:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

然而并没有什么卵用。去flask_sqlalchemy的init.py里面修改吧。
在init.py里面有init_app方法,修改下面的一行:

track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True)

然后保存,重新运行。搞定!

 

https://www.jianshu.com/p/5282a7525e52
https://www.jianshu.com/p/02c1a33ca1e9 

https://www.cnblogs.com/liangmingshen/p/9769975.html

https://www.jianshu.com/p/92890a4ec0cb

https://blog.csdn.net/qq_25730711/article/details/53690687

教程源码截取:

 
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", backref="user")


class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))
 

简单来说, relationship函数是sqlalchemy对关系之间提供的一种便利的调用方式, backref参数则对关系提供反向引用的声明。
假如没有relationship,我们只能像下面这样调用关系数据:

#给定参数User.name,获取该useraddresses
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    addresses = session.query(Address).filter_by(user_id=user.id).all()
    return addresses
 如果在User中使用relationship定义addresses属性的话,
addresses = relationship('Address')
 则我们可以直接在User对象中通过addresses属性获得指定用户的所有地址。
def get_addresses_from_user(user_name):
    user = session.query(User).filter_by(name=user_name).first()
    return user.addresses
 注意,在上面的addresses属性中我们并没有定义backref属性,
所以我们可以通过User对象获取所拥有的地址,但是不能通过Address对象获取到所属的用户.
 
>>> u = User()
>>> u.addresses
[]
>>> a = Address()
>>> a.user
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'Address' object has no attribute 'user'
 
 但是当我们有从Address对象获取所属用户的需求时,backref参数就派上用场了。
addresses = relationship('Address', backref='user')
>>> a = Address()
>>> a.user

大致原理应该就是:

sqlalchemy在运行时对Address对象动态的设置了一个指向所属User对象的属性,

这样就能在实际开发中使逻辑关系更加清晰,代码更加简洁了。

一言以蔽之: 

backref用于在关系另一端的类中快捷地创建一个指向当前类对象的属性。

 补充:

db.backref()是你需要对放置 backref的那一边的参数,

(在上例中为 Address类的 .user属性)指定参数时, 使用 backref()函数代替字符串, 常见的有 lazy='dynamic'(禁止自动查询, 用于添加过滤器)。

 backref用于在关系另一端的类中快捷地创建一个指向当前类对象的属性, 而当需要对那个属性指定参数时使用 db.backref()。

 

Flask-SQLAlchemy(MySQL)之一对多、一对一、多对多关系

一对多

  1. 创建两个模型
class Person(db.Model):
    __tablename__ = 'person'
    name = db.Column(db.String(20), primary_key=True)
    age = db.Column(db.Integer)
    birth = db.Column(db.Date)
    phone = db.Column(db.String(11), unique=True)

    # 使用关系函数定义关系属性
    cars = db.relationship('Car')

    def __repr__(self):
        return '姓名:{name} 年龄:{age} 生日:{birth} 电话:{phone}'.format(name=self.name, age=self.age, birth=self.birth,
                                                                 phone=self.phone)


class Car(db.Model):
    name = db.Column(db.String(10), primary_key=True)
    price = db.Column(db.Float)
    # 定义外键(表明.字段名)
    course_phone = db.Column(db.String(11), db.ForeignKey('person.phone'))

    def __repr__(self):
        return '汽车类型:{name} 总价:{price}'.format(name=self.name, price=self.price)
  1. 通过设置外键建立关系
@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00, course_phone='17777777777')
    car2 = Car(name='吉利*舰', price=43000.00, course_phone='17777777777')
    db.session.add(person)
    db.session.add(car1)
    db.session.add(car2)
    db.session.commit()
    click.echo('insert')

或者通过关系属性cars调用append建立关系

@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00)
    car2 = Car(name='吉利*舰', price=43000.00)
    db.session.add(person)
    person.cars.append(car1)
    person.cars.append(car2)
    db.session.commit()
    click.echo('insert')

通过remove解绑关系

person.cars.remove(car1)
db.session.commit()
  1. 查询
@app.cli.command()
def querypc():
    person = Person.query.first()
    click.echo(person.cars)

查询结果如下:

[汽车类型:五菱宏光 总价:55000.0, 汽车类型:吉利*舰 总价:43000.0]

建立双向关系

  1. 创建两个模型
class Person(db.Model):
    __tablename__ = 'person'
    name = db.Column(db.String(20), primary_key=True)
    age = db.Column(db.Integer)
    birth = db.Column(db.Date)
    phone = db.Column(db.String(11), unique=True)

    # 使用关系函数定义关系属性
    cars = db.relationship('Car', back_populates='person')

    def __repr__(self):
        return '姓名:{name} 年龄:{age} 生日:{birth} 电话:{phone}'.format(name=self.name, age=self.age, birth=self.birth,
                                                                 phone=self.phone)


class Car(db.Model):
    name = db.Column(db.String(10), primary_key=True)
    price = db.Column(db.Float)
    # 定义外键(表明.字段名)
    course_phone = db.Column(db.String(11), db.ForeignKey('person.phone'))

    person = db.relationship('Person', back_populates='cars')

    def __repr__(self):
        return '汽车类型:{name} 总价:{price}'.format(name=self.name, price=self.price)
  1. 建立关系
@app.cli.command()
def insertpc():
    person = Person(name='老赵', age=27, birth=datetime.datetime.now(), phone='17777777777')
    car1 = Car(name='五菱宏光', price=55000.00)
    car2 = Car(name='吉利*舰', price=43000.00)
    db.session.add(person)
    person.cars.append(car1)
    person.cars.append(car2)
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def queryc():
    car = Car.query.first()
    click.echo('{person} {car} '.format(person=car.person, car=car))

查询结果如下:

姓名:老赵 年龄:27 生日:2018-10-24 电话:17777777777 汽车类型:五菱宏光 总价:55000.0

一对一关系

  1. 创建两个模型,注意:创建一对一关系是通过将uselist设为False
class Husband(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    age = db.Column(db.Integer)

    wife = db.relationship('Wife', uselist=False)

    def __repr__(self):
        return '老公:{name} 年齡:{age}'.format(name=self.name, age=self.age)


class Wife(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    age = db.Column(db.Integer)
    husband_id = db.Column(db.Integer, db.ForeignKey('husband.id'))

    husband = db.relationship('Husband')

    def __repr__(self):
        return '老婆:{name} 年齡:{age}'.format(name=self.name, age=self.age)
  1. 建立关系,注意:一对一关系不能使用append,因为是单个记录,所以使用=
@app.cli.command()
def inserthw():
    husband = Husband(name='老王', age=24)
    wife = Wife(name='小红', age=18)
    db.session.add(husband)
    husband.wife = wife
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def queryhw():
    husband = Husband.query.first()
    click.echo('{husband} {wife}'.format(husband=husband, wife=husband.wife))

查询结果如下:

老公:老王 年齡:24 老婆:小红 年齡:18

多对多关系

  1. 建立存储多对多模型的外键对应关系的关联表
association_table = db.Table('association', db.Column('customer_id', db.Integer, db.ForeignKey('customer.id')),
                             db.Column('product_id', db.Integer, db.ForeignKey('product.id')))
  1. 建立两个模型,secondary设为关联表的名称,具体可查看relationship
class Customer(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    work = db.Column(db.String(20))
    products = db.relationship('Product', secondary=association_table, back_populates='customers')

    def __repr__(self):
        return '姓名:{name} 公司:{work}'.format(name=self.name, work=self.work)


class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10))
    price = db.Column(db.Float)
    customers = db.relationship('Customer', secondary=association_table, back_populates='products')

    def __repr__(self):
        return '产品类型:{name} 单价:{price}'.format(name=self.name, price=self.price)
  1. 建立关系
@app.cli.command()
def insertcp():
    customer1 = Customer(name='程老板', work='大兴有限公司')
    customer2 = Customer(name='李老板', work='弘成科技')
    customer3 = Customer(name='司马老板', work='小马加油有限公司')
    product1 = Product(name='丝绸', price=35.12)
    product2 = Product(name='铝合金', price=54.45)
    product3 = Product(name='盐', price=3.00)
    db.session.add(customer1)
    customer1.products.append(product1)
    customer1.products.append(product2)
    customer2.products.append(product2)
    customer3.products.append(product1)
    customer3.products.append(product3)
    product1.customers.append(customer1)
    product1.customers.append(customer3)
    product2.customers.append(customer2)
    product2.customers.append(customer1)
    product3.customers.append(customer3)
    db.session.commit()
    click.echo('insert')
  1. 查询
@app.cli.command()
def querycp():
    customer = Customer.query.first()
    click.echo('{customer}  购买了  {products}'.format(customer=customer, products=customer.products))

查询结果:

姓名:程老板 公司:大兴有限公司  购买了  [产品类型:铝合金 单价:54.45, 产品类型:丝绸 单价:35.12]

参考

 

初始化和配置

ORM(Object Relational Mapper) 对象关系映射。指将面对对象得方法映射到数据库中的关系对象中。
Flask-SQLAlchemy是一个Flask扩展,能够支持多种数据库后台,我们可以不需要关心SQL的处理细节,操作数据库,一个基本关系对应一个类,而一个实体对应类的实例对象,通过调用方法操作数据库。Flask-SQLAlchemy有很完善的文档。

Flask-SQLAlchemy是通过URL指定数据库的连接信息的。
初始化的两种方法如下(以连接Mysql数据库为例):

from flask_sqlalchemy import SQLAlchemy
from flask import FLask
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 
    "mysql://root:12345@localhost/test"
db = SQLAlchemy(app)

或者

from flask_sqlalchemy import SQLAlchemy
from flask import FLask
db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    return app

两者的区别在于:第一种不需要启动flask的app_context;但是第二种方法则需要,因为可能会创建多个Flask应用,但是我的理解是一般地开发时,Flask实例是延迟创建的,因为在运行时难以修改配置信息,这种方法符合这种情况。
Flask-SQLAlchemy的则需要在Flask.config中声明。更多详细信息需要查配置。例如配置信息中指出SQLAlchemy是可以绑定多个数据库引擎。再例如:在新浪SAE云平台开发个人博客时遇到gone away这种问题就需要添加SQLALCHEMY_POOL_RECYCLE信息,新浪开发者文档中有说明。


SQLALchemy处理 对象->关系

SQLAlchemy是如何处理对象到关系的?实例来自于数据库系统概论内容。

简单实例

创建学生students表

class Student(db.Model):
    __tablename__ = 'students' #指定表名
    sno = db.Column(db.String(10), primary_key=True)
    sname = db.Column(db.String(10))
    sage = db.Column(db.Integer)

API文档说明创建对象需要继承db.Model类关联数据表项,db.Model类继承Query类提供有数据查询方法;__tablename__指定数据库的表名,在Flask-SQLAlchemy中是可省的。Column指定表字段。

SQLAlchemy支持字段类型有:

类型名 python类型 说明
Integer int 普通整数,32位
Float float 浮点数
String str 变长字符串
Text str 变长字符串,对较长字符串做了优化
Boolean bool 布尔值
PickleType 任何python对象 自动使用Pickle序列化

来源于Simple ExampleFlask Web开发有更详细的内容。
其余的参数指定属性的配置选项,常用的配置选项如下:

选项名 说明
primarykey 如果设为True,表示主键
unique 如果设为True,这列不重复
index 如果设为True,创建索引,提升查询效率
nullable 如果设为True,允许空值
default 为这列定义默认值

如使用default默认time属性如下:

time = db.Column(db.Date, default=datetime.utcnow)

说明default可以接受lambda表达式。

一对多

按创建单张表的方法,创建学院Deptment表

class Deptment(db.Model):
    __tablename__ = 'deptments'
    dno = db.Column(db.Integer, primary_key=True)
    dname = Sname = db.Column(db.String(10),index=True)

学院和学生是一对多的关系。Flask-SQLAlchemy是通过db.relationship()解决一对多的关系。在Dept中添加属性,代码如下:

class Deptment(db.Model):
    ...
    students = db.relationship('Student', backref='dept')
    
    
class Student(db.Model):
    ...
    dept_no = db.Column(db.Integer, db.ForeignKey('deptments.dno'))

表的外键由db.ForeignKey指定,传入的参数是表的字段。db.relations它声明的属性不作为表字段,第一个参数是关联类的名字,backref是一个反向身份的代理,相当于在Student类中添加了dept的属性。例如,有Deptment实例dept和Student实例stu。dept.students.count()将会返回学院学生人数;stu.dept.first()将会返回学生的学院信息的Deptment类实例。一般来讲db.relationship()会放在这一边。

多对多

多对多的关系可以分解成一对多关系,例如:学生选课,学生与课程之间的关系:

sc = db.Table('sc',
    db.Column('sno', db.String(10), db.ForeignKey('students.sno'))
    db.Column('cno',db.String(10), db.ForeignKey('courses.cno'))
    )
    
Class Course(db.Model):
    __tablename__ = 'courses'
    cno = db.Column(db.String(10), primary_key=True)
    cname = db.Column(db.String(10), index=True)
    students = db.relationship('Student',
         secondary=sc,
         backref=db.backref('course',lazy='dynamic'),
         lazy='dynamic'
         )

sc表由db.Table声明,我们不需要关心这张表,因为这张表将会由SQLAlchemy接管,它唯一的作用是作为students表和courses表关联表,所以必须在db.relationship()中指出sencondary关联表参数。lazy是指查询时的惰性求值的方式,这里有详细的参数说明,而db.backref是声明反向身份代理,其中的lazy参数是指明反向查询的惰性求值方式,SQLAlchemy鼓励这种方式声明多对多的关系。

但是如果关联表中有自定义的字段,如sc表中添加成绩字段则需要更改表声明方式,将sc更改为继承db.Model的对象并设置sc:courses = 1:n 和sc:student = 1:n的关系。


SQLALchemy处理 关系->对象

Flask-SQLAlchemy查询中有详细的说明。创建关系后该如何查询到对象?

SQLAlchemy有查询过滤器如下:

过滤器 说明
filter() 把过滤器添加到原查询,返回新查询
filter_by() 把等值过滤器添加到原查询,返回新查询
limit() 使用指定值限制原查询返回的结果数量,返回新查询
offset() 偏移原查询返回的结果,返回新查询
order_by() 排序返回结果,返回新查询
groupby() 原查询分组,返回新查询

这些过滤器返回的结果都是一个新查询,我的理解是这些查询其实是生成的SQL语句,lazy的惰性求值方式也体现在查询上,而这些语句不能生成需要查询的对象,需要调用其他的方法生成对象。

SQL查询执行函数:

方法 说明
all() 以列表形式返回结果
first() 返回第一个结果,如果没有返回None
first_or_404() 返回第一个结果,如果没有抛出404异常
get() 返回主键对应记录,没有则返回None
get_or_404() 返回主键对应记录,如果没有抛出404异常
count() 返回查询结果数量
paginate() 返回paginate对象,此对象用于分页

使用flask-SQLAlchemy错误(一)

 

错误信息如下:

flask_sqlalchemy\__init__.py:800: UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.
  warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True to suppress this warning.')

错误信息提示的很明确,修改 SQLALCHEMY_TRACK_MODIFICATIONS 为True以移除这个警告。

尝试进行了如下修改:

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

然而并没有什么卵用。去flask_sqlalchemy的init.py里面修改吧。
在init.py里面有init_app方法,修改下面的一行:

track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True)

然后保存,重新运行。搞定!