【问题标题】:How do I make and access regex capture groups in Django without RawSQL?如何在没有 RawSQL 的情况下在 Django 中创建和访问正则表达式捕获组?
【发布时间】:2020-03-30 14:22:45
【问题描述】:

如何在不使用 RawSQL 的情况下使用 Regex 捕获组注释 Django 查询集,以便以后可以使用该值进行过滤和排序?

例如,在 PostgreSQL 中,我可以进行以下查询:

CREATE TABLE foo (id varchar(100));

INSERT INTO foo (id) VALUES ('disk1'), ('disk10'), ('disk2');

SELECT
    "foo"."id",
    CAST((regexp_matches("foo"."id", '^(.*\D)([0-9]*)$'))[2] AS integer) as grp2
FROM "foo"
ORDER BY "grp2"

dbfiddle

【问题讨论】:

    标签: sql regex django natural-sort


    【解决方案1】:

    您可以使用创建的自定义Func 类来使其工作,但我想以更好的方式实现,就像可以使用其他函数或注释等进行进一步处理的普通函数一样。喜欢Django ORM 生态系统中的“块”。

    我想从这个类的“测试版”开始:

    from django.db.models.expressions import Func, Value
    
    class RegexpMatches(Func):
        function = 'REGEXP_MATCHES'
    
        def __init__(self, source, regexp, flags=None, group=None, output_field=None, **extra):
            template = '%(function)s(%(expressions)s)'
    
            if group:
                if not hasattr(regexp, 'resolve_expression'):
                    regexp = Value(regexp)
    
                template = '({})[{}]'.format(template, str(group))
    
            expressions = (source, regexp)
            if flags:
                if not hasattr(flags, 'resolve_expression'):
                    flags = Value(flags)
    
                expressions += (flags,)
    
            self.template = template
    
            super().__init__(*expressions, output_field=output_field, **extra)
    

    以及一个完整的管理界面示例:

    from django.contrib.admin import ModelAdmin, register
    from django.db.models import IntegerField
    from django.db.models.functions import Cast
    from django.db.models.expressions import Func, Value
    
    from .models import Foo
    
    
    class RegexpMatches(Func):
        function = 'REGEXP_MATCHES'
    
        def __init__(self, source, regexp, flags=None, group=None, output_field=None, **extra):
            template = '%(function)s(%(expressions)s)'
    
            if group:
                if not hasattr(regexp, 'resolve_expression'):
                    regexp = Value(regexp)
    
                template = '({})[{}]'.format(template, str(group))
    
            expressions = (source, regexp)
            if flags:
                if not hasattr(flags, 'resolve_expression'):
                    flags = Value(flags)
    
                expressions += (flags,)
    
            self.template = template
    
            super().__init__(*expressions, output_field=output_field, **extra)
    
    
    @register(Foo)
    class Foo(ModelAdmin):
        list_display = ['id', 'required_field', 'required_field_string']
    
        def get_queryset(self, request):
            qs = super().get_queryset(request)
    
            return qs.annotate(
                required_field=Cast(RegexpMatches('id', r'^(.*\D)([0-9]*)$', group=2), output_field=IntegerField()),
                required_field_string=RegexpMatches('id', r'^(.*\D)([0-9]*)$', group=2)
            )
    
        def required_field(self, obj):
            return obj.required_field
    
        def required_field_string(self, obj):
            return obj.required_field_string
    

    正如您在我添加了 2 个注释和一个输出(如数字)和另一个输出(如普通字符串(字符)),当然,我们在管理界面中看不到它,但在 SQL 中可以看到被执行:

    SELECT "test_foo"."id" AS Col1,
                   ((REGEXP_MATCHES("test_foo"."id", '^(.*\D)([0-9]*)$'))[2])::integer AS "required_field", (REGEXP_MATCHES("test_foo"."id", '^(.*\D)([0-9]*)$'))[2] AS "required_field_string"
              FROM "test_foo"
    

    还有一个带有示例的屏幕截图:)

    Github gist 具有更好的源代码格式https://gist.github.com/phpdude/50675114aaed953b820e5559f8d22166

    【讨论】:

      【解决方案2】:

      从 Django 1.8 开始,您可以使用Func() expressions

      from django.db.models import Func
      
      class EndNumeric(Func):
          function = 'REGEXP_MATCHES'
          template = "(%(function)s(%(expressions)s, '^(.*\D)([0-9]*)$'))[2]::integer"
      
      qs = Foo.objects.annotate(
          grp2=EndNumeric('id'),
      ).values('id', 'grp2').order_by('grp2')
      

      参考:Get sorted queryset by specified field with regex in django

      【讨论】:

      • 只是想知道......这真的与数据库无关吗? (如果没有,我宁愿使用 RawSQL)
      • 在您的目标 RDBMS 中进行测试(对于 Django,它应该是 PostgreSQL)。除非您正在编写专门用于分发给可能使用各种数据库的人员或项目的东西,否则除非您犯了根本性错误,否则您可能会在某个时候切换后端的经常重复的想法基本上不会发生,例如在 SQLite 上构建您的平台。
      • @kungphu 我正好有那个用例,我正在分发一个被各种数据库使用的项目
      • @kungphu "project-side",我完全同意;但是,如果您正在开发一个公共应用程序,那将是一项要求,或者至少是非常可取的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-06
      • 1970-01-01
      相关资源
      最近更新 更多