10204

15.relationship 中惰性查询

1.试理解relationship(自己)

MySQL 是一个关系型数据库,关系型数据库最关键的就是关系.SQLAlchemy 作为一层ORM 对象关系映射,它是通过Model 的属性来模拟关系的.

10206

对于他们的映射

10206

大致被映射成了如上关系.

表与表之间的关系,直接映射成了Class 的一个relationship() 属性.表中字段之间的关系直接映射成一个一个class 类实例与另外 一个class 类实例之间的关系.

比如如下最简单的一对多的关系:

class Parent2(Base):  __tablename__ = 'Parent_1'  p_id = Column(Integer, primary_key=True)  p_name = Column(String(45))  children = relationship('Child2', back_populates='parent')

class Child2(Base):  __tablename__ = 'Child_1'  c_id = Column(Integer, primary_key=True)  c_name = Column(String(45))  p_id = Column(Integer, ForeignKey('Parent_1.p_id'))  parent=relationship('Parent2', back_populates='children')

那么Parent2 的实例p1,就和多个Child2 的实例产生了关系(通过外键),比如c1,c2.这样就完成了关系的抽象,也就是实现了面向对象的考虑.通过访问p1.children 就能访问到c1,c2,这样从ORM的角度简化了SQL语句的查询.

虽然底层实现的是SQL 语句.但是我们只用考虑面向对象层面就行.

这样又产生了新的问题,当访问p1.children 时,会拿到所有的在Child2 类中对应的实例.如果数据量过多,会非常消耗资源(比如内存吃紧),有时候,只想去拿到p1.children 对应的个别实例.这样就需要对Parent1.children 这个给属性做出控制.

Paren1.children 是一个relationship() 对象,也就是对relationship()做出控制.

SQL关系的角度看,这样就产生了惰性查询,也就是不返回所有的实例,而是返回一个Query 对象,让父类的属性再次操作Query 对象

2.简单验证lazy=dynamic

官网[1]

lazy 决定了 SQLAlchemy 什么时候从数据库中加载数据:,有如下四个值:(其实还有个noload不常用) select: (which is the default) means that SQLAlchemy will load the data as necessary in one go using a standard select statement. joined: tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement. subquery: works like ‘joined’ but instead SQLAlchemy will use a subquery. dynamic : is special and useful if you have many items. Instead of loading the items SQLAlchemy will return another query object which you can further refine before loading the items. This is usually what you want if you expect more than a handful of items for this relationship
  • select:默认值, 后台会用select语句一次性加载所有数据,即访问到属性的时候,就会全部加载该属性的数据.
  • joined:数据会被JOIN语句加载,即对关联的两个表进行join操作,从而获取到所有相关的对象.
  • dynamic:在访问属性的时候,并不在内存中加载数据,而是返回一个AppenderQuery 类对象,这个类继承自Query ,也就是会返回一个Query 对象.
  • subquery: 数据被用subquery子查询SQL语句加载
  • True :即select方法
  • False:即joined方法

现在来看一下一对多关系中的懒加载,首先一对多关系,说明父表的实例对象上关联了多个子表的实例对象.所以,一般会在父表上设置lazy='dynamic', 而不是子表.

设置如下关系的父表和子表

  • 父表:User,文章的作者,一个作者可以写多篇文章
  • 子表:Article,文章表,一个文章只能对应一个作者,它有一个外键连接在父表上u_id
from sqlalchemy import Column,Integer,Text,DECIMAL,String,ForeignKeyfrom sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmakerfrom sqlalchemy.orm import relationshipfrom sqlalchemy import DateTime

# 1.创建连接connect_msg = 'mysql+pymysql://root:2008.Cn123@192.168.0.101/test'engine=create_engine(connect_msg)

# 2.创建SessionSession = sessionmaker(bind=engine)# 实例化 Sessionsession = Session()

# 3.创建基类Base = declarative_base()

# 4.创建默认的一对多关系表, 默认的加载方式是 lazy='select'class User1(Base): __tablename__ = 'test_user1'

 id = Column(Integer, nullable=False, primary_key=True) name = Column(String(45))

 articles = relationship('Article1', back_populates='user')

class Article1(Base): __tablename__ = 'test_article1'

 id = Column(Integer, nullable=False, primary_key=True) name = Column(String(45)) create_time = Column(DateTime) u_id = Column(Integer, ForeignKey('test_user1.id'))

 user = relationship('User1', back_populates='articles')

# 5.创建懒加载 lazy='dynamic' 的一对多关系.class User2(Base): __tablename__ = 'test_user2'

 id = Column(Integer, nullable=False, primary_key=True) name = Column(String(45))

 articles = relationship('Article2', back_populates='user', lazy='dynamic')

class Article2(Base): __tablename__ = 'test_article2'

 id = Column(Integer, nullable=False, primary_key=True) name = Column(String(45)) create_time = Column(DateTime) u_id = Column(Integer, ForeignKey('test_user2.id'))

 user = relationship('User2', back_populates='articles')

# 6.创建表Base.metadata.drop_all(engine)Base.metadata.create_all(engine)

# 7.添加数据u1 = User1(id=1, name='Jack')

for i in range(1,100): a1 = Article1(id=i,name='xxxx',create_time='201{}-1{}-2{} 11:2{}:1{}'.format(i%2,i%2,i%2,i%2, i%2), u_id=1) u1.articles.append(a1) session.add(a1)

u2= User2(id=1, name='Jack')

for i in range(1,100): a1 = Article2(id=i,name='xxxx',create_time='201{}-1{}-2{} 11:2{}:1{}'.format(i%2,i%2,i%2,i%2,i%2), u_id=1) u2.articles.append(a1) session.add(a1)

session.commit()
  1. 首先,使用默认创建的一对多关系,查看Model 上的关系.
# 拿到 父表 映射 类 中的 第一条实例,也就是 第一个作者q1 = session.query(User1).first()

#  通过关系的映射 relationship 来查看子表中第一个作者写的所有文章q1.articles

"""SELECT test_article1.id AS test_article1_id, test_article1.name AS test_article1_name, test_article1.create_time AS test_article1_create_time, test_article1.u_id AS test_article1_u_id FROM test_article1 WHERE 1 = test_article1.u_id"""

然后对照mysql 的查询日志,查看它查找的SQL 语句

$ tail -10 /mysql_data/localhost_log...SELECT test_article1.id AS test_article1_id, test_article1.name AS test_article1_name, test_article1.create_time AS test_article1_create_time, test_article1.u_id AS test_article1_u_id FROM test_article1 WHERE 1 = test_article1.u_id

可以看到,它其实是查找了,所有u_id=1 的所有数据,上面添加了100条数据,它会把这100条数据全部输出.

假如,只想从这100条数据中查询出某一条数据,那上面的q1.articles 就不能满足需求了.这时候就需要懒查询.

  1. 使用lazy='dynamic' 查询
test2 = q2.articlesprint(type(test2))#

可以看到,当设置了lazy=dynamic 后,q2.articles 的操作就不是去数据库取出所有的数据了,而是返回一个AppenderQuery 对象.这里来查看一下这个对象.

>>> from sqlalchemy.orm.dynamic import AppenderQuery>>> help(AppenderQuery)

Help on class AppenderQuery in module sqlalchemy.orm.dynamic:class AppenderQuery(AppenderMixin, sqlalchemy.orm.query.Query)

可以看到,AppenderQuery 是继承自AppenderMixinsalalcemy.orm.query.Query 这两个类的.那么这个对象首先就拥有了Query 查询的所有方法(比如filter,count,limit,offset等).

# 查询一个实例q3 = test2.filter(Article2.create_time=='2011-11-21 11:21:11').filter(Article2.id==99).all()print(type(q3))print(q3)

"""SELECT test_article2.id AS test_article2_id, test_article2.name AS test_article2_name, test_article2.create_time AS test_article2_create_time, test_article2.u_id AS test_article2_u_id FROM test_article2 WHERE 1 = test_article2.u_id AND test_article2.create_time = '2011-11-21 11:21:11' AND test_article2.id = 99"""

然后对照mysql 的查询日志,查看它查找的SQL 语句

$ tail -10 /mysql_data/localhost_log...SELECT test_article2.id AS test_article2_id, test_article2.name AS test_article2_name, test_article2.create_time AS test_article2_create_time, test_article2.u_id AS test_article2_u_id FROM test_article2 WHERE 1 = test_article2.u_id AND test_article2.create_time = '2011-11-21 11:21:11' AND test_article2.id = 99

当需要查询更细致的数据时,这样可以节省更多的系统资源.这就是懒加载.


3.lazy='joined'

基于上面,在看一下,lazy='joined' 的操作:

# lazy='joined'class User3(Base):    __tablename__ = 'test_user3'

    id = Column(Integer, nullable=False, primary_key=True)    name = Column(String(45))

    articles = relationship('Article3', back_populates='user', lazy='joined')

class Article3(Base):    __tablename__ = 'test_article3'

    id = Column(Integer, nullable=False, primary_key=True)    name = Column(String(45))    create_time = Column(DateTime)    u_id = Column(Integer, ForeignKey('test_user3.id'))

    user = relationship('User3', back_populates='articles')

# 创建表Base.metadata.create_all(engine)

# 添加数据

u3 = User3(id=1, name='Jack')a3 = Article3(id=1, name='sdf', create_time='2011-11-21 11:21:11',u_id=1)a4 = Article3(id=2, name='sdf', create_time='2011-12-22 11:21:11',u_id=1)a5 = Article3(id=3, name='sdf', create_time='2012-11-21 11:21:11',u_id=1)a6 = Article3(id=4, name='sdf', create_time='2013-11-21 11:21:11',u_id=1)

u3.articles=[a3,a4,a5,a6]session.add(u3)session.commit()

mysql 数据库中查看数据

mysql root@192.168.101:test> select * from test_article3;+----+------+---------------------+------+| id | name | create_time         | u_id |+----+------+---------------------+------+| 1  | sdf  | 2011-11-21 11:21:11 | 1    || 2  | sdf  | 2011-12-22 11:21:11 | 1    || 3  | sdf  | 2012-11-21 11:21:11 | 1    || 4  | sdf  | 2013-11-21 11:21:11 | 1    |+----+------+---------------------+------+4 rows in setTime: 0.016smysql root@192.168.101:test> select * from test_user3;+----+------+| id | name |+----+------+| 1  | Jack |+----+------+1 row in setTime: 0.014s

查看关系查询

q4 = session.query(User3).first()test4 = q4.articlestype(test4)# sqlalchemy.orm.collections.InstrumentedList

可以看到,它返回的也是所有的查询结果,但是,查看mysql 的查询日志,可以看到它执行的查询语句是

SELECT anon_1.test_user3_id AS anon_1_test_user3_id, anon_1.test_user3_name AS anon_1_test_user3_name, test_article3_1.id AS test_article3_1_id, test_article3_1.name AS test_article3_1_name, test_article3_1.create_time AS test_article3_1_create_time, test_article3_1.u_id AS test_article3_1_u_idFROM (SELECT test_user3.id AS test_user3_id, test_user3.name AS test_user3_nameFROM test_user3 LIMIT 1) AS anon_1 LEFT OUTER JOIN test_article3 AS test_article3_1 ON anon_1.test_user3_id = test_article3_1.u_id

它首先执行了一个子查询,然后执行了一个外连接查询.

lazy 指定的参数不同,执行的效率也是不同的.

参考资料

[1]

官网: https://www.osgeo.cn/sqlalchemy/orm/relationship_api.html    

- END -

sql关联查询子表的第一条_SQLAlchemy(8)惰性查询相关推荐

  1. mySQL主表与子表一对多关系,left join关联查询子表中其中一条记录

    1.left join测试示例 SELECTa.id,a.create_name,a.create_time,b.id AS bId,b.charge_key,b.check_status FROMp ...

  2. mybatis查询子表

    今天来记录一下 mybatis自动查询子表数据 查询结果类如下: public class SoundPageVO {private String indexId;private String pro ...

  3. mysql查询每个用户第一条数据_MySQL数据库订单表按用户邮箱字段分组查询每个用户的第一条记录...

    程序开发或者一些数据统计时,在MySQL中使用GROUP BY分组是很常用的SQL语句.那么,如果如下的简单示例订单数据表,我们现需要使用GROUP BY分组后查询每个用户的第一个订单记录,应该如何实 ...

  4. mysql查询子表的语句_MySQL基本SQL语句之单表查询、多表查询和子查询

    一.简单查询: 1.基本语法: SELECT * FROM tb_name;//查询全部 SELECT field1,field2 FROM tb_name; //投影 SELECT [DISTINC ...

  5. SQL 连接 表,取副表的第一条数据

    两个表是一(A表)对多(B表)的关系,join的时候,B表只对应显示第一条数据(某些条件).sql该如何写? 表A Aid title days 1 清远二天游 2 2 东莞一天游 1 3 深圳小梅沙 ...

  6. sql 取重复key中的第一条_SQL每日一题

    写在前面 SQL每日一题是用牛客网的sqlite库的题目进行深度讲解(感觉进度慢的小伙伴可以自行前往刷题),这个系列要开启日更计划,每日一题,一起进步学习. 题目描述 查找最晚入职员工的所有信息,为了 ...

  7. access 查询符合条件的第一条记录

    有个定时上传数据库中某些记录的需求. 定时任务中,如果查出所有符合条件的数据并上传,如果数据过多,可能不易处理,那么就限定每次只查询符合条件的一条或几条记录.经测试,access中SQL写法举例如下: ...

  8. mysql 查询重复记录 取第一条_取出重复记录的第一条

    这几天在查询购买记录,其中一个需求就是查出来客户首次购买的产品时间和产品名称.说白了,就是在一段时间内,去取出来用户的第一次购买记录,再清楚一些就是在一堆重复的记录中取出第一条购买记录. 这个问题捉只 ...

  9. mysql打平子表_对于oracle进行简单树查询(递归查询)

    DEPTID PAREDEPTID NAME NUMBER NUMBER CHAR (40 Byte) 部门id 父部门id(所属部门id) 部门名称 通过子节点向根节点追朔. Sql代码 selec ...

最新文章

  1. 你真的了解AI吗?AI将怎么改变我们的生活?
  2. layui文本框填充值_layui框架常用输入框介绍
  3. 大竹中学2021高考成绩查询,2021年大竹中学升学率高不高?
  4. Linux 查看进程在哪个CPU上运行
  5. Xcode 4.4 的新特性 | LLVM 4.0 的新语法
  6. Mongodb java 例子
  7. 「Vue」vuex 的使用
  8. thinkphp中I方法
  9. 14 MySQL--事务函数与流程控制
  10. Jquery中parentsUntil函数调用最容易犯的三个错误
  11. 服务器监视Zabbix 5.0 - 安装部署
  12. 1月3日 升 级gazebo7
  13. 2021华为软挑赛题_思路分析——实时更新,做多少更多少(一)
  14. web服务之源码编译安装LAMP
  15. jQuery表单提交
  16. 红帽6.2 DOS无盘 NetWare 制作
  17. 如何将YouTube和其他网络视频投射到Kodi(例如Chromecast)
  18. 古城钟楼微博地支报时程序铛,100行代码实现,价值一天20万粉丝
  19. vue自定义数字键盘
  20. android微信卡,微信无响应怎么弄 让别人微信卡死的小技巧(PC/安卓适用)

热门文章

  1. win10+ubuntu双系统修复ubuntu启动引导
  2. 简述数学建模的过程_【数学建模的基本工作流程】作业帮
  3. TensorFlow学习笔记--第三节张量(tensor)及其定义方法
  4. mysql视图改造实体表_数据库视图改了对基表
  5. Javascript第五章切换层效果、复选框的全选十三课
  6. SimpleDateFormat 格式化日期
  7. php imagemagick安装,在CentOS上为PHP安装Imagick和ImageMagick
  8. mysql 分区 目的_MySQL分区表最佳实践
  9. python3面向对象_Python3面向对象编程
  10. 简单记录一次ORA-00600 kcratr_nab_less_than_odr