如何使用Django ORM过滤CharField中大于指定语义版本的记录?
如何用Django ORM过滤大于指定语义版本的记录
我需要处理这样一个需求:请求传入形如1.1.1.1的CharField格式语义版本值,要从查询集中筛选出版本字段大于该值的对象。比如数据库里有这些版本记录:
0.0.0.0 0.0.0.1 1.0.0.0 1.1.1.1 1.1.1.2 2.2.2.2
当指定版本为1.1.1.1时,筛选结果应该是[1.1.1.2, 2.2.2.2]。
目前我是用Python标准库的packaging.version模块来实现过滤的,但想知道能不能直接通过Django ORM完成这个操作,避免先拉取所有数据再过滤的开销。
当前实现代码:
from packaging.version import Version # ... 其他业务代码 qs = ApplicationVersion.objects.filter( operating_system=user_os.upper() ) valid_versions = [ i.version for i in qs if Version(i.version) > Version(user_app_version) ]
可行的Django ORM实现方案
1. 利用数据库数组函数(PostgreSQL专属)
如果你的项目用的是PostgreSQL,可以借助string_to_array函数把版本号拆成整数数组,直接比较数组大小:
from django.db.models import Func, F, Value class StringToArray(Func): function = 'string_to_array' template = "%(function)s(%(expressions)s, '.')::int[]" # 筛选逻辑 target_version = user_app_version valid_versions_qs = ApplicationVersion.objects.filter( operating_system=user_os.upper(), StringToArray(F('version')) > StringToArray(Value(target_version)) ).values_list('version', flat=True)
2. 手动拆分版本段做多条件过滤(兼容多数据库)
如果需要兼容MySQL、SQLite等多种数据库,可以把版本号按.拆分,逐个段构建比较条件:
from django.db.models import Func, F, Q, Value from django.db import models # 先把目标版本拆成整数列表 target_parts = list(map(int, user_app_version.split('.'))) qs = ApplicationVersion.objects.filter(operating_system=user_os.upper()) # 定义拆分版本段的函数(以PostgreSQL的split_part为例,MySQL可替换为SUBSTRING_INDEX) class SplitPart(Func): function = 'split_part' def __init__(self, expression, delimiter, position, **extra): super().__init__( expression, Value(delimiter), Value(position), output_field=models.IntegerField(), **extra ) # 构建多段比较条件 condition = ( SplitPart(F('version'), '.', 1) > target_parts[0] | (SplitPart(F('version'), '.', 1) == target_parts[0]) & (SplitPart(F('version'), '.', 2) > target_parts[1]) | (SplitPart(F('version'), '.', 1) == target_parts[0]) & (SplitPart(F('version'), '.', 2) == target_parts[1]) & (SplitPart(F('version'), '.', 3) > target_parts[2]) | (SplitPart(F('version'), '.', 1) == target_parts[0]) & (SplitPart(F('version'), '.', 2) == target_parts[1]) & (SplitPart(F('version'), '.', 3) == target_parts[2]) & (SplitPart(F('version'), '.', 4) > target_parts[3]) ) valid_versions_qs = qs.filter(condition).values_list('version', flat=True)
3. 预存版本数字段(最优长期方案)
如果频繁需要做版本比较,最稳妥高效的方式是在模型里新增整数字段存储版本各段的值,保存时自动拆分版本号存入:
from django.db import models from django.db.models import Q # 模型定义 class ApplicationVersion(models.Model): version = models.CharField(max_length=20) version_major = models.IntegerField() # 对应x.x.x.x的第一个x version_minor = models.IntegerField() # 第二个x version_patch = models.IntegerField() # 第三个x version_build = models.IntegerField() # 第四个x operating_system = models.CharField(max_length=50) def save(self, *args, **kwargs): # 保存时自动拆分版本号到对应字段 parts = list(map(int, self.version.split('.'))) self.version_major = parts[0] self.version_minor = parts[1] self.version_patch = parts[2] self.version_build = parts[3] super().save(*args, **kwargs) # 查询时直接用整数比较,性能最优 target_parts = list(map(int, user_app_version.split('.'))) valid_versions_qs = ApplicationVersion.objects.filter( operating_system=user_os.upper(), (Q(version_major__gt=target_parts[0]) | Q(version_major=target_parts[0], version_minor__gt=target_parts[1]) | Q(version_major=target_parts[0], version_minor=target_parts[1], version_patch__gt=target_parts[2]) | Q(version_major=target_parts[0], version_minor=target_parts[1], version_patch=target_parts[2], version_build__gt=target_parts[3])) ).values_list('version', flat=True)
内容的提问来源于stack exchange,提问作者rzar
相关产品推荐
相关产品推荐

