Как объединить набор запросов в один результат без повторения? - PullRequest
7 голосов
/ 11 октября 2019

Моя модель:

class GroupBase(models.Model):
    """
    Predefined base group name
    """
    YesNo = (
        ('Yes', 'Yes'),
        ('No', 'No')
    )
    name = models.CharField(max_length=32, unique=True)
    parent = models.CharField(max_length=20)
    is_revenue = models.CharField(max_length=3, choices=YesNo, default='No')
    affects_trading = models.CharField(max_length=3, choices=YesNo, default='No')
    is_debit = models.CharField(max_length=3, choices=YesNo, default='No')

    def __str__(self):
        return self.name

class LedgerGroup(models.Model):
    """
    Ledger Group Master
    """

    group_name = models.CharField(max_length=50)
    group_base = models.ForeignKey(GroupBase, on_delete=models.DO_NOTHING, related_name='base_group', default=1)

    def __str__(self):
        return self.group_name

class LedgerMaster(models.Model):
    """
    Ledger Master
    """
    ledger_name = models.CharField(max_length=80)  # unique together with company using meta
    ledger_group = models.ForeignKey(LedgerGroup, on_delete=models.DO_NOTHING, related_name='group_ledger')
    closing_balance = models.DecimalField(default=0.00, max_digits=20, decimal_places=2)

    def __str__(self):
        return self.ledger_name

У меня есть следующие запросы:

group_debit_positive = GroupBase.objects.filter(base_group__group_ledger__company=company,is_debit__exact='Yes',base_group__group_ledger__closing_balance__gt=0).annotate(
        total_debit_positive=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_debit_negative=Sum(0,output_field=FloatField()),
        total_credit_positive=Sum(0,output_field=FloatField()),
        total_credit_negative=Sum(0,output_field=FloatField()))

group_debit_negative = GroupBase.objects.filter(base_group__group_ledger__company=company,is_debit__exact='Yes',base_group__group_ledger__closing_balance__lt=0).annotate(
        total_debit_positive=Sum(0,output_field=FloatField()),
        total_debit_negative=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_credit_positive=Sum(0,output_field=FloatField()),
        total_credit_negative=Sum(0,output_field=FloatField()))

group_credit_positive = GroupBase.objects.filter(base_group__group_ledger__company=company,is_debit__exact='No',base_group__group_ledger__closing_balance__gt=0).annotate(
        total_debit_positive=Sum(0,output_field=FloatField()),
        total_debit_negative=Sum(0,output_field=FloatField()),
        total_credit_positive=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_credit_negative=Sum(0,output_field=FloatField()))

group_credit_negative = GroupBase.objects.filter(base_group__group_ledger__company=company,is_debit__exact='No',base_group__group_ledger__closing_balance__lt=0).annotate(
        total_debit_positive=Sum(0,output_field=FloatField()),
        total_debit_negative=Sum(0,output_field=FloatField()),
        total_credit_positive=Sum(0,output_field=FloatField()),
        total_credit_negative=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0)))

Я выполнил объединение всех запросов:

final_set = group_debit_positive.union(group_debit_negative,group_credit_positive,group_credit_negative)

Я хочучтобы получить один результат, а не повторение в моем наборе запросов объединения.

Например:

всякий раз, когда я пытаюсь напечатать полученный набор запросов

for g in final_set:
        print(g.name,'-',g.total_credit_positive,'-',g.total_credit_negative)

Я получаюрезультаты, подобные этому:

Sundry Creditors - 0.0 - -213075
Purchase Accounts - 0.0 - 0.0
Sundry Creditors - 95751.72 - 0.
Sales Accounts - 844100.0 - 0.0
Sales Accounts - 0.0 - -14000.0

Как видите, Sales Account повторяется дважды.

Я хочу что-то вроде следующего:

Sundry Creditors - 0.0 - -213075
Purchase Accounts - 0.0 - 0.0
Sundry Creditors - 95751.72 - 0.
Sales Accounts - 844100.0 - -14000.0

Как остановитьповторение результатов и превращение его в единый результат.

Любая идея, кто-нибудь, как это сделать?

РЕДАКТИРОВАТЬ

Я еще пытался использовать "|"Чтобы объединить набор запросов, он успешно объединяется без повторения, но добавляет результат с тем же именем.

Я сделал следующее:

final_queryset = group_debit_positive | group_debit_negative | group_credit_positive | group_credit_negative

Результат получается следующим образом:

Sundry Creditors - -213075 - 0.0
Purchase Accounts - 0.0 - 0.0
Sundry Creditors - 95751.72 - 0.
Sales Accounts - 830100 - 0.0

Это добавляет результат как результат Sales Accounts становится 830100(844100.0 + (-14000.0).

Может кто-нибудь помочь мне выяснить, что я делаю неправильно.

Спасибо

Ответы [ 5 ]

4 голосов
/ 19 октября 2019

Вместо Sum можно использовать аргумент filter с другим объектом Q для каждой аннотации. Также используйте метод values набора запросов, чтобы сгруппировать выходные данные по полю name, чтобы в выходных данных не было отдельных записей с одинаковыми именами:

final_set = GroupBase.objects.filter(
    base_group__group_ledger__company=company).values('name').annotate(
    total_debit_positive=Sum('base_group__group_ledger__closing_balance', output_field=FloatField(),
        filter=Q(is_debit__exact='Yes', base_group__group_ledger__closing_balance__gt=0)),
    total_debit_negative=Sum('base_group__group_ledger__closing_balance', output_field=FloatField(),
        filter=Q(is_debit__exact='Yes', base_group__group_ledger__closing_balance__lt=0)),
    total_credit_positive=Sum('base_group__group_ledger__closing_balance', output_field=FloatField(),
        filter=Q(is_debit__exact='No', base_group__group_ledger__closing_balance__gt=0)),
    total_credit_negative=Sum('base_group__group_ledger__closing_balance', output_field=FloatField(),
        filter=Q(is_debit__exact='No', base_group__group_ledger__closing_balance__lt=0))
)
for g in final_set:
    print(
        g['name'], g['total_debit_positive'], g['total_debiit_negative'],
        g['total_credit_positive'], g['total_credit_negative'], sep=' - '
    )
4 голосов
/ 15 октября 2019

Можете ли вы попробовать создать один набор запросов, используя Case и When вместо объединения, например:

from django.db.models import Case, When

final_set = GroupBase.objects.filter(base_group__group_ledger__company=company).annotate(
    total_debit_positive=Case(
        When(is_debit__exact='Yes', base_group__group_ledger__closing_balance__gt=0, then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_debit_negative=Case(
        When(is_debit__exact='Yes', base_group__group_ledger__closing_balance__lt=0, then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_credit_positive=Case(
        When(is_debit__exact='No', base_group__group_ledger__closing_balance__gt=0, then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_credit_negative=Case(
        When(is_debit__exact='No', base_group__group_ledger__closing_balance__lt=0, then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    )
0 голосов
/ 22 октября 2019

Большое спасибо всем. Наконец-то я нашел решение своего вопроса и подумал о том, чтобы опубликовать его для справки других.

Это мои запросы:

    group_debit_positive = GroupBase.objects.filter(base_group__group_ledger__company=company, is_debit__exact='Yes', base_group__group_ledger__closing_balance__gt=0).annotate(
        total_debit_positive_opening=Sum(0, output_field=FloatField()),
        total_debit_negative_opening=Sum(0, output_field=FloatField()),
        total_credit_positive_opening=Sum(0, output_field=FloatField()),
        total_credit_negative_opening=Sum(0, output_field=FloatField()),
        total_debit_positive=Coalesce(
            Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_debit_negative=Sum(0, output_field=FloatField()),
        total_credit_positive=Sum(0, output_field=FloatField()),
        total_credit_negative=Sum(0, output_field=FloatField())).exclude(name__exact='Primary').exclude(name__exact='Current Assets').values(
            'name',
            'total_debit_positive',
            'total_debit_negative',
            'total_credit_positive',
            'total_credit_negative')

    group_debit_negative = GroupBase.objects.filter(base_group__group_ledger__company=company, is_debit__exact='Yes', base_group__group_ledger__closing_balance__lt=0).annotate(
        total_debit_positive_opening=Sum(0, output_field=FloatField()),
        total_debit_negative_opening=Sum(0, output_field=FloatField()),
        total_credit_positive_opening=Sum(0, output_field=FloatField()),
        total_credit_negative_opening=Sum(0, output_field=FloatField()),
        total_debit_positive=Sum(0, output_field=FloatField()),
        total_debit_negative=Coalesce(
            Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_credit_positive=Sum(0, output_field=FloatField()),
        total_credit_negative=Sum(0, output_field=FloatField())).exclude(name__exact='Primary').exclude(name__exact='Current Assets').values(
            'name',
            'total_debit_positive',
            'total_debit_negative',
            'total_credit_positive',
            'total_credit_negative')

    group_credit_positive = GroupBase.objects.filter(base_group__group_ledger__company=company, is_debit__exact='No', base_group__group_ledger__closing_balance__gt=0).annotate(
        total_debit_positive_opening=Sum(0, output_field=FloatField()),
        total_debit_negative_opening=Sum(0, output_field=FloatField()),
        total_credit_positive_opening=Sum(0, output_field=FloatField()),
        total_credit_negative_opening=Sum(0, output_field=FloatField()),
        total_debit_positive=Sum(0, output_field=FloatField()),
        total_debit_negative=Sum(0, output_field=FloatField()),
        total_credit_positive=Coalesce(
            Sum('base_group__group_ledger__closing_balance'), Value(0)),
        total_credit_negative=Sum(0, output_field=FloatField())).exclude(name__exact='Primary').exclude(name__exact='Current Assets').values(
            'name',
            'total_debit_positive',
            'total_debit_negative',
            'total_credit_positive',
            'total_credit_negative')

    group_credit_negative = GroupBase.objects.filter(base_group__group_ledger__company=company, is_debit__exact='No', base_group__group_ledger__closing_balance__lt=0).annotate(
        total_debit_positive_opening=Sum(0, output_field=FloatField()),
        total_debit_negative_opening=Sum(0, output_field=FloatField()),
        total_credit_positive_opening=Sum(0, output_field=FloatField()),
        total_credit_negative_opening=Sum(0, output_field=FloatField()),
        total_debit_positive=Sum(0, output_field=FloatField()),
        total_debit_negative=Sum(0, output_field=FloatField()),
        total_credit_positive=Sum(0, output_field=FloatField()),
        total_credit_negative=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))).exclude(name__exact='Primary').exclude(name__exact='Current Assets').values(
            'name',
            'total_debit_positive',
            'total_debit_negative',
            'total_credit_positive',
            'total_credit_negative')

Сначала я вернусьнабор запросов, содержащий словарь.

Затем я выполнил объединение запросов, как в моем вопросе

final_queryset = group_debit_positive.union(
        group_debit_negative, group_credit_positive, group_credit_negative, all=True).order_by('name')

Затем я отфильтровал группу запросов по их name, чтобы избежать дублирования результатов (как аннотации). / агрегация не может быть выполнена в объединении наборов запросов)

grouped_group_name = itertools.groupby(
        final_queryset, key=lambda x: (x['name']))

Тогда

result = []
    for group_key, group_values in grouped_group_name:
        positive_debit = decimal.Decimal(0.0)
        negative_debit = decimal.Decimal(0.0)
        positive_credit = decimal.Decimal(0.0)
        negative_credit = decimal.Decimal(0.0)

        # Storing each value into a variable to render a list later
        for each_group in group_values:
            positive_debit += decimal.Decimal(
                each_group['total_debit_positive'])
            negative_debit += decimal.Decimal(
                each_group['total_debit_negative'])
            positive_credit += decimal.Decimal(
                each_group['total_credit_positive'])
            negative_credit += decimal.Decimal(
                each_group['total_credit_negative'])

        # Making a list of items fetched to render in Django Template
        result.append({
            'group_name': group_key,
            'positive_debit': round(positive_debit, 2),
            'negative_debit': round(negative_debit, 2),
            'positive_credit': round(positive_credit, 2),
            'negative_credit': round(negative_credit, 2),
        })

Таким образом, я смог найти решение моей проблемы.

Iзнаю, это немного длительный процесс.

Спасибо всем за помощь.

0 голосов
/ 18 октября 2019

.annotate ()

Вы должны выполнить запрос на модели LedgerMaster, чтобы получить предпочтительные результаты, чтобы избежать повторения. используйте Case, When для получения различных значений на основе встроенного фильтра

from django.db.models import Case, When

final_set = LedgerMaster.objects.filter(
    company=company
).values('ledger_group__group_base__name').annotate(
    total_debit_positive=Case(
        When(ledger_group__group_base__name__is_debit__exact='Yes', closing_balance__gt=0,
             then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_debit_negative=Case(
        When(ledger_group__group_base__name__is_debit__exact='Yes', closing_balance__lt=0,
             then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_credit_positive=Case(
        When(ledger_group__group_base__name__is_debit__exact='No', closing_balance__gt=0,
             then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
    total_credit_negative=Case(
        When(ledger_group__group_base__name__is_debit__exact='No', closing_balance__lt=0,
             then=Coalesce(Sum('base_group__group_ledger__closing_balance'), Value(0))),
        default=Value(0),
        output_field=FloatField()
    ),
).order_by()

выходные данные будут выглядеть так:

[
    {
        'ledger_group__group_base__name': <group_base_nameA>,
        'total_debit_positive': <amount>,
        'total_debit_negative': <amount>,
        'total_credit_positive': <amount>,
        'total_credit_negative': <amount>,
    },
    {
        'ledger_group__group_base__name': <group_base_nameB>,
        'total_debit_positive': <amount>,
        'total_debit_negative': <amount>,
        'total_credit_positive': <amount>,
        'total_credit_negative': <amount>,
    },
    ....
]

0 голосов
/ 11 октября 2019

Есть ли order_by в class Meta вашей GroupBase модели?

, что может привести к нарушениям [0] при агрегировании / аннотировании. Попробуйте добавить пустой .order_by() к вашим запросам и посмотрите, решит ли это проблему.

[0] https://docs.djangoproject.com/en/2.2/topics/db/aggregation/#interaction-with-default-ordering-or-order-by)

...