在Django queryset上注释以逗号分隔的相关项列表

时间:2016-06-24 15:34:24

标签: django django-models

从表面上看,这似乎是一个奇怪的事情要求,但我需要一个逗号分隔的模型相关项目串。从任何教程中获取作者/书籍模型示例,这是我目前正在做的事情:

authors = Authors.objects.all().prefetch_related('books')
for author in authors:
    author.book_titles = ', '.join([book.title for book in author.books.all()])

它并不重,但感觉多余。就像数据库可以做到的那样。在一个理想的世界里,我觉得我应该能够用这些花哨的新database functions之一来注释这个。这是一个幻想的例子,使用了一个名为Joiner(..)的虚构函数:

Authors.objects.annotate(book_titles=Joiner('books__title', separator=', ')

这可能吗?如果是这样,怎么样?

4 个答案:

答案 0 :(得分:5)

from django.db.models import Aggregate, CharField, Value

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(expressions)s)'

    def __init__(self, expression, delimiter, **extra):
        output_field = extra.pop('output_field', CharField())
        delimiter = Value(delimiter)
        super(GroupConcat, self).__init__(
            expression, delimiter, output_field=output_field, **extra)

    def as_postgresql(self, compiler, connection):
        self.function = 'STRING_AGG'
        return super(GroupConcat, self).as_sql(compiler, connection)

用法:

Author.objects.annotate(book_titles=GroupConcat('book__title', ', '))

Custom aggregates。这应该适用于SQLite,MySQL和PostgreSQL。

答案 1 :(得分:0)

接受的答案没有在MySQL中正确传递分隔符

这允许您设置分隔符并指定DISTINCT:

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'
    template = '%(function)s(%(distinct)s%(expressions)s%(separator)s)'

    def __init__(self, expression, distinct=False, separator=None, **extra):
        output_field = extra.pop('output_field', CharField())
        distinct = 'DISTINCT ' if distinct else ''
        separator = " SEPARATOR '{}'".format(separator) if separator is not None else ''
        super(GroupConcat, self).__init__(
        expression, separator=separator, distinct=distinct, output_field=output_field, **extra)

另见:GROUP_CONCAT equivalent in Django

答案 2 :(得分:0)

我没有代表发表评论,但我应该补充一点,尽管如果使用MySQL,两个答案都可以使用(只需为接受的答案传递定界符“”即可),group_concat_max_len默认设置为1024。这意味着您用逗号分隔的字符串将被截断为1024个字符。要增加此值,请使用myql命令:

SET GLOBAL group_concat_max_len = 1000000;

这会将所有数据库中的所有group_concat_max_len设置为100万个字符,这在大多数情况下就足够了。

答案 3 :(得分:0)

不确定这是否对任何人都有用,但我需要它能够处理不同的值(在聚合列表中仅将每个项目包括一次)。我不知道这是否适用于postgres。

class GroupConcat(Aggregate):
    function = 'GROUP_CONCAT'

    def __init__(self, expression, delimiter=', ', distinct=False, **extra):
        output_field = extra.pop('output_field', CharField())
        delimiter = Value(delimiter)
        self.template = '%(function)s(DISTINCT %(expressions)s)' if distinct else '%(function)s(%(expressions)s)'
        super(GroupConcat, self).__init__(
            expression, delimiter, output_field=output_field, **extra)

    def as_postgresql(self, compiler, connection):
        self.function = 'STRING_AGG'
        return super(GroupConcat, self).as_sql(compiler, connection)