【问题标题】:Using Postgres `unnest` and `substr` together in Django 2.2在 Django 2.2 中一起使用 Postgres `unnest` 和 `substr`
【发布时间】:2020-09-07 07:46:07
【问题描述】:

我正在尝试编写一个 Django 注释来添加从 Django 模型中单个 CharField 中的单词生成的首字母缩写。例如:

  • The ABC Company 将变为 TAC
  • A Company Making Everything 将变为 ACME

我使用的是 PostgreSQL 12.2。我从https://stackoverflow.com/a/33312435/3443946找到了以下SQL:

select name, string_agg(substr(initials, 1,1)||'.', ' ') initials
from (
  select name, unnest(string_to_array(full_name,' ')) initials
  from app_model
) sub group by 1;

这个 SQL 工作(或多或少),现在我正在尝试使其适应 Django。我基于https://brownbears.tistory.com/496 实现了 StringToArray(稍作修改以删除内部变量引用和可能的翻译错误):

class StringToArray(Func):
  function = 'string_to_array'
  output_field = ArrayField(CharField(max_length=1))
  arity = 1
  template = "%(function)s(%(expressions)s,'%(delimiter)s','%(null)s')"

  def __init__(self, expression, delimiter=',', null=''):
    super().__init__(expression, delimiter=delimiter, null=null)

Unnest 看起来很简单,如下所示:

class Unnest(Func):
  function = 'unnest'

上面的两个命令我是这样实现的:

Account.objects.annotate(
  unnested_strings=Unnest(
    StringToArray(
      F('name'), delimiter=' '
    )
  )
).values()

以上正确返回以下查询集(为简单起见显示为 dict):

[
  {
    'name': 'The ABC Company',
    'unnested_strings': 'The',
    ...
  },
  {
    'name': 'The ABC Company',
    'unnested_strings': 'ABC',
    ...
  },
  {
    'name': 'The ABC Company',
    'unnested_strings': 'Company',
    ...
  },
  ...
]

效果很好,所以这是我用SubstrStringAgg 实现其余部分的尝试:

Account.objects.annotate(
  initials=StringAgg(
    Substr(
      StringToArray(
        F('name'), delimiter=' '
      ), 1, 1
    ), delimiter=''
  )
).values()

当我运行上述代码时,我收到以下psycopg2 错误:

django.db.utils.NotSupportedError: aggregate function calls cannot contain set-returning function calls
LINE 1: ...T "accounts_account"."name", STRING_AGG(SUBSTRING(unnest(str...
                                                             ^
HINT:  You might be able to move the set-returning function into a LATERAL FROM item.

从错误消息来看,Substr 似乎不能直接作用于Unnest 结果,这就是为什么原始 SQL 在使用 Substr 之前有一个group by 子句的原因。但是,我不太清楚如何在 Django 中做到这一点。有什么建议吗?

【问题讨论】:

    标签: django postgresql orm


    【解决方案1】:

    不幸的是,我找不到仅使用纯基于 ORM 的数据库调用的好方法。我最终没有弄乱原始 SQL,而是将其设为模型上的非必填字段,并在模型保存时自动填充:

    # models.py
    
    def get_initials(text):
      return ''.join([_[0] for _ in text.split(' ') if _])
    class Account(models.Model):
      name = models.CharField(max_length=100)
      initials = models.CharField(max_length=50, blank=True)
    
      def save(self, update_fields=None, *args, **kwargs):
        initials = get_initials(self.name)
        if self.initials != initials:
          self.initials = initials
    
          # skip update_fields if this is a new object
          # ._state is actually public, just named that way to prevent conflicts
          if not self._state.adding:
            if update_fields:
              update_fields.append('initials')
            else:
              update_fields = ['initials']
    
        super().save(update_fields=update_fields, *args, **kwargs)
    

    添加的update_fields 逻辑并不是绝对必要的,但我在 Django 信号中使用该参数来判断更新模型中发生了什么变化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-10-13
      • 1970-01-01
      • 2011-12-17
      • 2013-04-09
      • 2016-06-03
      • 2016-02-01
      • 2021-06-18
      • 1970-01-01
      相关资源
      最近更新 更多