如何实现mysql语句效果 : “SELECT field as x…” with Django ORM
参考地址
场景描述
最近因业务需求,这边需要做一个数据导出,需要用到两张表的数据来满足,A
表和B
表,属于一对多的数据关系,因此首先想到的就是django orm 的外键关联查询,借助django-restful
和django-filter
的强大组合,轻松实现这个组合查询。数据量的问题,考虑到耗时,采用celery来异步完成数据处理,所以就借助django orm的功能,获取映射的原生sql,将sql传入tasks任务中,让数据库执行和导出放在task下来处理。
一切按照计划进行,期间还是遇到一些问题。
class B(models.Model):
id = models.AutoField(db_column='f_id', primary_key=True)
email = models.CharField(db_column='f_email', max_length=32, default='', help_text=u'邮箱')
address = models.CharField(db_column='f_address', max_length=32, default='', help_text=u'地址')
class A(models.Model):
id = models.AutoField(db_column='f_id', primary_key=True)
name = models.CharField(db_column='f_name', max_length=32, default='', help_text=u'姓名')
b= models.ForeignKey(B, db_column='f_email', to_field='email', db_constraint=False, help_text=u'邮箱')
from django_filters import rest_framework as filters
class AListFilter(filters.FilterSet):
"""请款记录"""
address = filters.CharFilter(name='b__address')
class Meta:
model = A
fields = ('address',)
问题:
A与B两表在orm中未使用外键关系ForeignKey标记
虽然在数据库中有关联两表的唯一字段,但是并不是一个无意义的唯一id值,而是我们的业务单号,若添加外键,影响到原表的该字段存储方式。这里采用了继承,将业务单号字段单独抽取,其余的公共字段置为一个抽象model下,这样,采用继承,一个子类保持原名称和业务号字段不变,另一个采用ForeignKey。外键关联的字段需要是唯一的,默认指定为关联model的主键id,这里通过to_field
指定为需要关联的唯一字段。这样就可以使用orm的关联查询,通过select_related
来正向关联查询。
django-filter 如何接受关联表的参数筛选
在我们定义的AListFilter
中,address字段的指定查询为b__address
,即可自动关联执行查询。
获取orm映射sql
原因为简单的通过A.objects.all().query
就可以获取到sql语句,这里其实拿到的是一个Query对象,需要通过str()获取其sql字符串,一切都是很顺利,但是在运行时,查询报错了,原来是这样拼接出来的sql对于传参的占位符%s
未使用引号包裹,导致报错参数类型不是整形。通过对Query类查询,发现__str__
魔法方法是将sql和变量拼接得到的,sql, params = A.objects.all().query.sql_with_params()
其中的sql占位符确实没有引号,MySQL-python的执行函数,支持对占位sql和参数传入,会自动处理这个%s
占位符的问题。
映射sql中两表字段名一样的字段,在执行结束后获取的值被后面的覆盖
对于A和B中的字段名一样问题,django的聚合函数annotate
可以实现将sql指定别名,A.objects.all().annotate(other_name=F('name'))
,这样,就可以避免字段名一样被覆盖的问题。