首页 > 数据库 >09-SQL语句的生成-概览

09-SQL语句的生成-概览

时间:2023-02-03 11:55:37浏览次数:41  
标签:count qs SQL company 09 概览 base sql select

orm 与 SQL

orm 的最重要的作用是帮助我们从繁重的 sql 书写中解放出来,依据灵活的查询的条件动态生成 sql。毕竟数据库最终查询数据也是只认 sql。所以搞明白 sql 的生成尤为重要。理解了 sql 的生成,就可以在使用中注意到很多细节,有时候 orm 的查询结果可能并不是我们所想的那样。

举个我们公司遇到的例子。
我们公司一共有5个分公司,每个公司都有自己的销售。而我们的客户会由销售去对接,并且一个客户只能属于一个销售,客户与销售之间的关系是多对一,并且客户所属的销售可以变更,变更的时候,新的销售可以不是原来的销售所属的分公司。假设客户A的第一个销售是甲,那么我们称之为客户A是销售甲开发成功的。如果客户A后面变更了对接的销售,为乙,我们不能说客户A是销售乙开发成功的。这个开发成功是有次序问题的。

客户表 Partner
销售表 Saler
客户销售关系表 SalerPartner
数据库:Postgresql

现在需要做一个统计:统计每一个分公司开发成功的客户数量。还有个限定条件,前端页面上针对每一个分公司都有一个版块儿,因此需要按照分公司单独统计5次。

如果使用 orm 大概率是这么写

base_qs = SalerPartner.objects.order_by('create_time').distinct(partner_id)
res = base_qs.filter(saler__company='深圳').count()
res = base_qs.filter(saler__company='北京').count()
res = base_qs.filter(saler__company='上海').count()
res = base_qs.filter(saler__company='常州').count()
res = base_qs.filter(saler__company='廊坊').count()

但是这么写一定会有问题,假如客户一共有 500 个,那么五个分公司的所有开发成功的客户数量也一定是500,可是上面的写法五个数量加起来极大概率是大于 500 的。

我们以深圳为例,上面的 orm 翻译成 sql 会是什么样子

select count(*)
from(
  select distinct (on a.id) a.*
  from SalerPartner a
  inner join Saler b on a.saler_id=b.id
  where b.company='深圳'
  order by a.create_time
) c

我们需要知道 SQL 的执行顺序是 where,join, order_by, select, limit。更为详细的请自行百度,这几个关键字的执行顺序是这样的。因此上面的 sql 是先过滤出深圳的销售与客户关系记录,在按照记录创建时间排序,然后去重,最后计算数据条数。

假如一个客户第一销售属于深圳,第二销售属于北京,那么在对深圳和北京的分别统计中肯定都会出现这个客户,就会导致五个分公司的客户总数加起来超过整个系统中的客户总数。这个不符合统计要求。

如果使用 sql,应该如何避免这种情况呢?

select count(*)
from SalerPartner
where id in (
  select distinct on (a.partner_id) a.id
  from SalerPartner a
  inner join Saler b on a.saler_id=b.id
  order by a.create_time
) 
  and b.company='深圳'

orm 则是这样:

qs_ids = SalerPartner.objects.order_by('create_time').distinct(partner_id).values_list('id', flat=True)
qs_base = SalerPartner.objects.filter(pk__in=qs_ids)
res = base_qs.filter(saler__company='深圳').count()
res = base_qs.filter(saler__company='北京').count()
res = base_qs.filter(saler__company='上海').count()
res = base_qs.filter(saler__company='常州').count()
res = base_qs.filter(saler__company='廊坊').count()

这样的 orm 会生成一个子查询,最终结果符合要求。

其实对于 orm,还有很多使用上的细节,只有足够了解他,才能正确使用他。当然,看源码,学习框架的思想和原理本身就是一间有趣的事,也是快速提高开发能力的一种捷径。

上一篇博客讲述到了 as_sql 这个关键方法,他是如何将 Query 对象翻译成 SQL 的。本篇文章将会对其主要流程做一个概述,然后通过这篇概述文章发散去弄明白整个 orm 的细节。由整体到局部,这是学习编程的重要思想。

as_sql 的返回

在调用 as_sql 的方法处,可以明确的确认,他的返回值是一个 sql 和一个 参数元组。

我们在 as_sql 方法返回之前,打上断点,重新调试,可以发现如下的截图内容

result 是一个列表,他存储了一个 sql 的每一个关键字及其对应的内容。其实 sql 的每一个部分都是很清晰的。

select 确定输出字段
from 确定获取表数据的主要字段
join on 确定关联的表, 以及关联字段
where 负责过滤数据, 或者是限制数据
limit 负责输出数据的条数
group 确定分组字段,此时 select 衔接的字段要么是
having 确定对聚合之后的数据进行过滤

一条完整的 sql 其实就这么些,而子查询其实是 where 的扩展,我们可以先搞明白单层查询是怎么回事。至于 SQL 校验完全可以交给数据库来做,框架本身来参与校验,是多此一举的事情。

再看 result 这个列表完全就是一条sql 的顺序拆分,然后简单的用空格拼接。浏览整个 as_sql 的源码,也可以发现,其实 as_sql 就是如此做的,基于 Query 示例,计算每一部分,放入 result 列表,最后拼接。所以接下来的分析也是基于此来做。

测试代码

这个测试代码将会触发 select,where,group by, having, limit, join, from 等关键字

断点

断点主要打在 as_sql 即将返回的时候,使用的 QuerySet 依旧是我们重写的 QuerySet2,这是为了方便调试。

关键字对应

可以看到每一个关键字在调试器中都有对应。下面我们将会对每一个部分进行逐一的讲解,并且是按照 从 select 到 limit 的顺序

select

select概览

select 后面衔接的查询显示字段来自两处。

【1】这里怎么触发我尚不清楚,后续如果搞清楚了再来回补这一段
【2】大概率是最终的 values 方法内传递的元素,extra_select 目前还不清楚。依据别名对查询显示字段名进行 as 修饰,从而组合查询语句。
【3】select 是一个列表,每一个列表都是一个三元组,其中第一个元素是一个 Col 的列对象,他描述了某个字段的具体的位置,api[包名].Province[Model].name[字段],这个元素被舍弃了,因为 sql 语句中不需要什么对象。第二个元素是一个二元组,二元组两个元素分别是对应的查询语句和对应的参数值[select 部分没有用到],第三个元素是字段别名,如果是Model中已经定义的字段名肯定没有别名,如果使用了 annotate 设置了字段别名,则第三个元素肯定有值。例如这里的 citys.annotate 不光可以对新的数据列进行命名,也可以对已有的字段进行重命名。

self.select 怎么来的

这里我们重点查看 self.select,他是怎么来的。self 对应的类是当前的 django.db.models.sql.SQLCompiler。在初始化方法内,他被设为默认的 None, 因此他一定是在哪里被重新赋值了,只需要在文件内搜索 self.select,一个一个排查就可以知道了。

最终我们发现了这样一条调用轨迹,get_select,应该就是他了。

get_select 返回的数据是 ret,这个对应的就是前面提到的 self.select, 而 ret 来自于 select,因此我们只需要知道 get_select 方法内的 select 元素是怎么生成的就好了。

select 列表的元素主要来自【1,2,3】三大块儿

【1】跟 QuerySet2.extra_select 字典有关,这里为空,我也没弄清楚这个是什么东西,后续搞清楚了再回补这篇文章
【2】这里,QuerySet2 的默认列和显示查询列不能都有值,否则会报错,这里我们猜想默认列是当我们没有调用 values 方法或者 values 方法参数列表为空的时候,指向的所有的列,如果是聚合查询则是聚合字段以及聚合之后的字段,如果不是聚合查询则是所有的表字段加别名字段。
【3】是别名列,别名列又包括聚合之后的列。

我们不能详细展开 default_cols 是什么时候修改了默认值,这样整篇文章结构就不清晰了,我们还是继续讨论 self.query.selectself.query.annotation_select, 这两个属性是何时修改和添加的。

self.query.select

标签:count,qs,SQL,company,09,概览,base,sql,select
From: https://www.cnblogs.com/yaowy001/p/17076847.html

相关文章

  • mysql必知必会——GROUP BY和HAVING
    GROUPBY语法可以根据给定数据列的每个成员对查询结果进行分组统计,最终得到一个分组汇总表。select子句中的列名必须为分组列或列函数,列函数对于groupby子句定义的每个组......
  • Ubantu 安装 MySQL 8.0 教程
    第一步:更新软件包$sudoaptupdate第二步:安装mysql8.0$sudoaptinstallmysql-server-8.0-y第三步:查询安装的mysql版本$mysql--versionmysqlVer8.0......
  • MySQL创建用户和授权
    MySQL创建用户和授权--语法createuser'用户名'@'主机'identifiedby'密码';--实例createuser'rayfoo'@'%'identifiedby'123456';--刷新,立即生效flushpr......
  • SQL Server新建维护计划
    1.连接数据库,点击维护计划->新建维护计划2.选择最左边的备份数据库、清除历史记录、清除维护。选中以后直接往右拖到设计器图面。3.效果4.选中备份数据库右击->编辑......
  • HDU1098 Ignatius's puzzle (数学归纳法)
    Description:Ignatiusispooratmath,hefallsacrossapuzzleproblem,sohehasnochoicebuttoappealtoEddy.thisproblemdescribesthat:......
  • SqlServer2008R2锁机制
    1.性能低下的update会怎么样?(1).使用原始的person表,插入6条数据,由于是4000字节,所以两条数据就是一个数据页droptabledbo.person;createtableperson(idintidentity,nam......
  • py09函数简介
     函数的返回值#deffunc():#return'asfjsfda'#res=func()#print(res)#函数内要想返回给调用者值必须用关键字return"""不写return只写return写returnNone......
  • SQL语句的执行顺序怎么理解?
    书写顺序SELECT->DISTINCT->FROM->JOIN->ON->WHERE->GROUPBY->HAVING->ORDERBY->LIMIT执行顺序FROM->JOIN->ON->WHERE->GROUPBY->H......
  • Hibernate下分页语句第一页和第二页sql语句不一致问题解决方法
    <propkey="hibernate.dialect">新增类OracleDialect</prop>OracleDialectextendsorg.hibernate.dialect.Oracle10gDialect重写getLimitString方法sql......
  • mysql 基础
    通用语法以及分类:DDL:数据定义语言,用来定义数据库对象(数据库,表,字段)DML:数据操作语言,用来对数据库表中的数据进行增删改DQL:数据查询语言,用来查询数据库中表的记录DCL:数......