如何在 Django 数据库中存储版本号字符串以便它们正确 comparable/sortable

How do I store version number strings in a django database so they are correctly comparable/sortable

我是 developing/maintaining/curating 从各种可穿戴研究设备收集的测试结果数据库。每个设备都有三个主要组件,每个组件都有两个版本号(固件和硬件)。我正在使用 Django 应用程序为数据库提供 Web 界面。版本号表示为直接整数或三元组(主要、次要、内部版本)。整数很容易处理,我显然可以将三元组存储为字符串,但作为字符串它们不会正确排序或正确比较,例如,如果我只想要固件版本低于 [=22 的设备产生的测试结果=].

由于第二个 'decimal point' 分隔符,我不能使用浮点数。我想也许可以通过将其存储为日期来进行黑客攻击,但这会将次要号码限制为小于 12,并将版本号限制为小于 29,此外我 知道 这是一个可怕的解决方案。我什至不应该承认我想到了它。

缺少一些 PL/SQL 扩展数据库以提供正确处理字符串的比较函数,有没有简单的方法可以做到这一点?如果没有,我什至可以将我的自定义 SQL 函数与 django 一起使用吗?

将它们存储为零填充字符串:

>>> def sortable_version(version):
...     return '.'.join(bit.zfill(5) for bit in version.split('.'))
... 
>>> sortable_version('1.1')
'00001.00001'
>>> sortable_version('2')
'00002'
>>> sortable_version('2.1.10')
'00002.00001.00010'
>>> sortable_version('10.1')
'00010.00001'
>>> sortable_version('2') > sortable_version('1.3.4')
True
>>> sortable_version('10') > sortable_version('2')
True
>>> sortable_version('2.3.4') > sortable_version('2')
True

并且您始终可以使用这种零填充格式显示常规版本:

>>> def normalize_version(padded_version):
...     return '.'.join(bit.lstrip('0') for bit in padded_version.split('.'))
... 
>>> normalize_version('00010')
'10'
>>> normalize_version('00002.00001.00010')
'2.1.10'

您要找的是"natural sort"。

另一种实现方式是使用数据库排序。这是 Postgres 的示例:

class VersionRecordManager(models.Manager):

    def get_queryset(self):
        return super().get_queryset().extra(
            select={
                'natural_version':
                    "string_to_array(                     "  
                    "   regexp_replace(                   "  # Remove everything except digits
                    "       version, '[^\d\.]+', '', 'g'  "  # and dots, then split string into
                    "   ), '.'                            "  # an array of integers.
                    ")::int[]                             "
            }
        )

    def available_versions(self):
        return self.filter(available=True).order_by('-natural_version')

    def last_stable(self):
        return self.available_versions().filter(stable=True).first()

class VersionRecord(models.Model):
    objects = VersionRecordManager()
    version = models.CharField(max_length=64, db_index=True)
    available = models.BooleanField(default=False, db_index=True)
    stable = models.BooleanField(default=False, db_index=True)