Django不通过外键实现多表关联查询

by:授客 QQ1033553122

测试环境

Win 10

 

Python 3.5.4

 

Django-2.0.13.tar.gz

需求

不通过外键,使用django orm语法实现多个表之间的关联查询,类似如下sql的查询效果:

SELECT tb_project_version.*, tb_sprint.name, tb_project.name

FROM tb_project_version

JOIN tb_sprint ON tb_sprint.id=tb_project_version.sprint_id

JOIN tb_project ON tb_project.id=tb_project_version.project_id

数据表Model设计

class Sprint(models.Model):

id = models.AutoField(primary_key=True, verbose_name='自增id')

name = models.CharField(max_length=50, verbose_name='迭代名称')

...略

class Meta:

db_table = 'tb_sprint'

        verbose_name = '产品迭代表'

        verbose_name_plural = verbose_name

class Project(models.Model):

id = models.AutoField(primary_key=True, verbose_name='自增id')

name = models.CharField(max_length=50, verbose_name='项目名称')

...略

class Meta:

db_table = 'tb_project'

        verbose_name = '项目表'

        verbose_name_plural = verbose_name

 

class ProjectVersion(models.Model):

id = models.AutoField(primary_key=True, verbose_name='自增id')

name = models.CharField(max_length=50, verbose_name='版本名称')

project_id = models.IntegerField(verbose_name='关联的项目ID')

sprint_id = models.IntegerField(verbose_name='关联的迭代ID')

...略

class Meta:

db_table = 'tb_project_version'

        verbose_name = '项目版本表'

        verbose_name_plural = verbose_name

实现方法1-通过extra api函数实现

如下,带背景色部分的内容为核心

serializers.py

#!/usr/bin/env python

# -*- coding:utf-8 -*-

 

from rest_framework import serializers

from backend.models import ProjectVersion

 

# ProjectVersion model 序列化器

class ProjectVersionSerializer(serializers.ModelSerializer):

project = serializers.CharField(required=True)

sprint = serializers.CharField(required=True)

class Meta:

model = ProjectVersion

fields = '__all__'

read_only_fields = ['project', 'sprint']

说明:如上,如果使用了django rest framework序列化,则需要为其序列化器添加model中不存在的字段,否则序列化后还是看不到对应的目标字段

project_version_views.py

#!/usr/bin/env python

# -*- coding:utf-8 -*-

 

__author__ = '授客'

 

from rest_framework.views import APIView

from rest_framework.response import Response

from rest_framework import status

from backend.models import ProjectVersion

from backend.serializers import ProjectVersionSerializer

class ProjectVersionListAPIView(APIView):

'''

    项目视图-版本管理

    '''

    # 查询列表数据

    def get(self, request, format=None):

result = {}

try:

params =  request.GET

page_size = int(params.get('pageSize'))

page_no = int(params.get('pageNo'))

name = params.get('name')

project_id = params.get('projectId')

sort = params.get('sort')

if sort:

sort_list = sort.split(',')

else:

sort_list = ['-id']

startIndex = (page_no - 1) * page_size

endIndex = startIndex + page_size

filters = {'is_delete':0}

if name:

filters['name__startswith'] = name

if project_id:

filters['project_id'] = project_id

projectVersions = ProjectVersion.objects.filter(**filters).extra(

select={'project': 'SELECT tb_project.name FROM tb_project WHERE tb_project.id = tb_project_version.project_id',

'sprint':'SELECT tb_sprint.name FROM tb_sprint WHERE tb_sprint.id = tb_project_version.sprint_id'},

)

rows = projectVersions.order_by(*sort_list)[startIndex:endIndex]

rows = ProjectVersionSerializer(rows, many=True).data

total = projectVersions.count()

result['msg'] =  '获取成功'

            result['success'] =  True

            result['data'] = {}

result['data']['rows'] = rows

result['data']['total'] = total

return Response(result, status.HTTP_200_OK)

except Exception as e:

result['msg'] =  '%s' % e

result['success'] =  False

            return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

说明:

projectVersions.order_by(*sort_list)[startIndex:endIndex]

等价于

SELECT (SELECT tb_project.name FROM tb_project WHERE tb_project.id = tb_project_version.project_id) AS `project`,

(SELECT tb_sprint.name FROM tb_sprint WHERE tb_sprint.id = tb_project_version.sprint_id) AS `sprint`,

`tb_project_version`.`id`,

`tb_project_version`.`name`,

`tb_project_version`.`project_id`,

`tb_project_version`.`sprint_id`,

...略

FROM `tb_project_version`

WHERE `tb_project_version`.`is_delete` = 0

ORDER BY `tb_project`.`id` DESC LIMIT 10 # 假设startIndex=0, endIndex=10

projectVersions.count()

等价于

SELECT COUNT(*) AS `__count` FROM `tb_project_version`

WHERE `tb_project_version`.`is_delete` = 0

上述查询代码的另一种实现

projectVersions =  Project.objects.filter(**filters).extra(

select={'project:'tb_project.name',

'sprint':' tb_sprint.name',

tables=['tb_project', 'tb_sprint'],

where=['tb_project.id=tb_project_version.project_id', 'tb_sprint.id = tb_project_version.sprint_id']

)

rows = projectVersions.order_by(*sort_list)[startIndex:endIndex]

rows = ProjectVersionSerializer(rows, many=True).data

total = projectVersions.count()

projectVersions.order_by(*sort_list)[startIndex:endIndex]

等价于

SELECT (tb_project.name) AS `project`,

(tb_sprint.name) AS `sprint`,

`tb_project_version`.`id`,

`tb_project_version`.`name`,

`tb_project_version`.`project_id`,

`tb_project_version`.`sprint_id`,

...略

FROM `tb_project_version`

WHERE `tb_project_version`.`is_delete` = 0 AND (tb_project.id=tb_project_version.project_id) AND (tb_sprint.id = tb_project_version.sprint_id)

ORDER BY `tb_project`.`id` DESC LIMIT 10 # 假设startIndex=0, endIndex=10

projectVersions.count()

等价于

SELECT COUNT(*) AS `__count` FROM `tb_project_version` , `tb_project` , `tb_sprint` WHERE `tb_project_version`.`is_delete` = 0 AND (tb_project.id=tb_project_version.project_id) AND (tb_sprint.id = tb_project_version.sprint_id)

实现方法2-通过django rest framework实现

serializers.py

#!/usr/bin/env python

# -*- coding:utf-8 -*-

 

from rest_framework import serializers

from backend.models import ProjectVersion

from backend.models import Sprint

from backend.models import Project

# ProjectVersion model 序列化器

class ProjectVersionSerializer(serializers.ModelSerializer):

project = serializers.SerializerMethodField()

    sprint = serializers.SerializerMethodField()

def get_sprint(self, obj):

"""

        :param obj: 当前ProjectVersion的实例

        """

        current_project_version = obj

sprint = Sprint.objects.filter(id=current_project_version.sprint_id).first()

if sprint:

return sprint.name

else:

return '--'

 

def get_project(self, obj):

"""

        :param obj: 当前ProjectVersion的实例

        """

        current_project_version = obj

project = Project.objects.filter(id=current_project_version.project_id).first()

if project:

return project.name

else:

return '--'

 

    class Meta:

model = ProjectVersion

fields = '__all__'

read_only_fields = ['project', 'sprint']

 

project_version_views.py

#!/usr/bin/env python

# -*- coding:utf-8 -*-

 

__author__ = '授客'

 

from rest_framework.views import APIView

from rest_framework.response import Response

from rest_framework import status

from backend.models import ProjectVersion

from backend.serializers import ProjectVersionSerializer

class ProjectVersionListAPIView(APIView):

'''

    项目视图-版本管理

    '''

    # 查询列表数据

    def get(self, request, format=None):

result = {}

try:

params =  request.GET

page_size = int(params.get('pageSize'))

page_no = int(params.get('pageNo'))

name = params.get('name')

project_id = params.get('projectId')

sort = params.get('sort')

if sort:

sort_list = sort.split(',')

else:

sort_list = ['-id']

startIndex = (page_no - 1) * page_size

endIndex = startIndex + page_size

filters = {'is_delete':0}

if name:

filters['name__startswith'] = name

if project_id:

filters['project_id'] = project_id

rows = ProjectVersion.objects.filter(**filters).order_by(*sort_list)[startIndex:endIndex]

rows = ProjectVersionSerializer(rows, many=True).data

total = ProjectVersion.objects.filter(**filters).count()

result['msg'] =  '获取成功'

            result['success'] =  True

            result['data'] = {}

result['data']['rows'] = rows

result['data']['total'] = total

return Response(result, status.HTTP_200_OK)

except Exception as e:

result['msg'] =  '%s' % e

result['success'] =  False

            return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

方法3-通过raw函数执行原生sql

以下是项目中的一个实例,和本文上述内容没有任何关联,关键部分背景已着色,笔者偷懒,不做过多解释了,简单说下下面这段代码对用途:

主要是实现类似以下查询,获取指定分页对数据以及满足条件的记录记录总数。

SELECT tb_project.*, project_name_associated, project_id_associated, platform FROM tb_project

LEFT JOIN tb_project_associated ON tb_project.id=tb_project_associated.project_id

ORDER BY id DESC

LIMIT 0,10

from rest_framework.views import APIView

from rest_framework.response import Response

from rest_framework import status

from backend.models import Project

from backend.serializers import ProjectSerializer

import logging

logger = logging.getLogger('mylogger')

class ProjectListAPIView(APIView):

'''

    项目视图-项目管理-项目列表

    '''

 

    # 查询列表数据

    def get(self, request, format=None):

result = {}

try:

params =  request.GET

page_size = int(params.get('pageSize'))

page_no = int(params.get('pageNo'))

name = params.get('name')

project_status = params.get('status')

sort = params.get('sort')

order_by = 'id desc'

            if sort:

order_by = sort

            startIndex = (page_no - 1) * page_size

            where = 'WHERE tb_project.is_delete=0 '

            filters = {'is_delete':0}

if name:

filters['name__startswith'] = name

where += 'AND locate("%s", name) ' % name

if project_status:

       where += "AND status='%s'" % project_status

sql = 'SELECT tb_project.id, COUNT(1) AS count FROM tb_project LEFT JOIN tb_project_associated ON tb_project.id=tb_project_associated.project_id '

            query_rows = Project.objects.raw(sql)

total = query_rows[0].__dict__.get('count') if query_rows else 0

sql =  'SELECT tb_project.*,project_name_associated, project_id_associated, platform FROM tb_project LEFT JOIN tb_project_associated ON tb_project.id=tb_project_associated.project_id ' \

'%s ORDER BY %s ' \

'LIMIT %s,%s ' % (where,order_by, startIndex, page_size)

query_rows = Project.objects.raw(sql)

rows = []

for item in query_rows:

item.__dict__.pop('_state')

item.__dict__['create_time'] = item.__dict__['create_time'].strftime('%Y-%m-%d %H:%M:%S')

item.__dict__['update_time'] = item.__dict__['update_time'].strftime('%Y-%m-%d %H:%M:%S')

item.__dict__['begin_time'] = item.__dict__['begin_time'].strftime('%Y-%m-%d')

item.__dict__['end_time'] = item.__dict__['end_time'].strftime('%Y-%m-%d')

rows.append(item.__dict__)

            result['msg'] =  '获取成功'

            result['success'] =  True

            result['data'] = {}

result['data']['rows'] = rows

result['data']['total'] = total

return Response(result, status.HTTP_200_OK)

except Exception as e:

result['msg'] =  '%s' % e

result['success'] =  False

            return Response(result, status.HTTP_500_INTERNAL_SERVER_ERROR)

参考链接

https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.extra

https://www.jianshu.com/p/973971880da7

Django 不通过外键实现多表关联查询的更多相关文章

  1. django模型中有外键关系的表删除相关设置

    0904自我总结 django模型中有外键关系的表删除相关设置 一.一对一 例如有Author.AuthorDetail两表 author = models.OneToOneField(to='Aut ...

  2. mysql 外键约束及表关联

    一.MYSQL中的约束 1.主键:primary key 唯一非空的特性并且可以优化查询速度 2.外键:foreign key 外键的作用保证2个或2个以上的数据表的数据一致性和完整性 3.唯一:un ...

  3. django模型中, 外键字段使用to_filed属性 指定到所关联主表的某个字段

    在django项目的开发过程中,在设计模型时一开始将主键设置成了一个自定义的字段,但是在创建搜索索引时却发现必须要存在一个id的字段,并且为主键(不知道是否是项目一开始就这样配置的原因), 但此时表结 ...

  4. MySQL的外键,修改表,基本数据类型,表级别操作,其他(条件,通配符,分页,排序,分组,联合,连表操作)

    MySQL的外键,修改表,基本数据类型,表级别操作,其他(条件,通配符,分页,排序,分组,联合,连表操作): a.创建2张表 create table userinfo(nid int not nul ...

  5. (4)MySQL的外键(不同表之间的数据关联)

    问题:下列这张表中部门等列名下输入的数据没有约束,那么可以随便填写符合规则的数据但是不符合实际需求的值,这样就造成了不符合规则的数据在表中存在,外键就是为了解决这个问题,管理员可以在另一张表中设置好符 ...

  6. MySQL数据库(4)_MySQL数据库外键约束、表查询

    一.外键约束 创建外键 --- 每一个班主任会对应多个学生 , 而每个学生只能对应一个班主任 ----主表 CREATE TABLE ClassCharger( id TINYINT PRIMARY ...

  7. 吃货眼中的sqlalchemy外键和连表查询

    前言 使用数据库一个高效的操作是连表查询,一条查询语句能够查询到多个表的数据.在sqlalchem架构下的数据库连表查询更是十分方便.那么如何连表查询?以及数据库外键对连表查询有没有帮助呢?本篇文章就 ...

  8. MySQL数据库 外键,级联, 修改表的操作

    1.外键: 用来建立两张表之间的关系 - 一对多 - 多对多 - 一对一 研究表与表之间的关系: 1.定义一张 员工部门表 id, name, gender, dep_name, dep_desc - ...

  9. SQLAlchemy03 /外键、连表关系

    SQLAlchemy03 /外键.连表关系 目录 SQLAlchemy03 /外键.连表关系 1.外键 2.ORM关系以及一对多 3.一对一的关系 4.多对多的关系 5.ORM层面的删除数据 6.OR ...

  10. SQLAlchemy(三):外键、连表关系

    SQLAlchemy03 /外键.连表关系 目录 SQLAlchemy03 /外键.连表关系 1.外键 2.ORM关系以及一对多 3.一对一的关系 4.多对多的关系 5.ORM层面的删除数据 6.OR ...

随机推荐

  1. PHP 中使用 ElasticSearch 的最佳实践(上)

    PHP 中使用 ElasticSearch 的最佳实践 引言 PHP 开发者其实使用到 ES 的情况并不多,因为开发的大多数项目可能都没有快速模糊搜索的需求. 即使有这样的需求,用 MySQL 的 l ...

  2. 基于FPGA的计算器设计---第一版

    欢迎各位朋友关注"郝旭帅电子设计团队",本篇为各位朋友介绍基于FPGA的计算器设计---第一版. 功能说明: 1. 计算器的显示屏幕为数码管. 2. 4x4矩阵键盘作为计算器的输入 ...

  3. iOS 系统级别录屏方式调研

    p.p1 { margin: 0; font: 20px ".PingFang SC"; color: rgba(69, 69, 69, 1) } p.p2 { margin: 0 ...

  4. Java常用的三个方法 `wait ` `notify` `notifyAll`

    常用的三个方法 wait notify notifyAll wait();方法使当前线程进入等待状态,直到另一个线程调用该对象的notify()或notifyAll()方法来唤醒它 notify(); ...

  5. HiveSQL 工作实战总结

    记录一些工作中有意思的统计指标,做过一些简化方便大家阅读,记录如有错误,欢迎在评论区提问讨论~ 问题类型 连续问题 两种思路 第一种:日期减去一列数字得出日期相同,主要是通过row_number窗口函 ...

  6. react类组件 组件传值

    class Cmp1 extends React.Component{ render(){ return ( <div>{ this.props.name } -- 我是一个类</d ...

  7. P2868

    Sightseeing Cows G 我们先考虑如何求平均乐趣值. 1.总乐趣为 \(\sum^n_{i = 1}f_i \times s_i\),其中 \(f_i\) 为第 \(i\) 个点的乐趣值 ...

  8. 1024程序员节,写最棒的coding,做最靓的仔

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 1024程序员节,写最棒的coding,做最靓的仔 日期: ...

  9. .NET个人博客-使用Back进行消息推送

    使用Back推送消息到你的iPhone 前言 我的好友看了我的博客,给我提了个需求,让我搞个网站通知,我开始以为就是评论回复然后发送邮件通知.不过他告诉我网站通知是,当有人评论或者留言后,会通知到我这 ...

  10. Android 各层架构

    Android应用框架层和硬件抽象层以及底层之间的关系 1. JNI技术: (1).JNI技术简单的说就是在本地Java语言声明本地方法和加载动态链接库(.so文件) (2).动态链接库(.so文件) ...