简体   繁体   中英

How can I filter a Django queryset using just a CASE or a COALESCE expression?

I'm basing a Django queryset on a functioning postgres query that has the following WHERE condition:

... WHERE COALESCE("project_story"."owner_id" = [INSERT_USER_ID], "project_story"."published_date" IS NOT NULL)

In english: if the user is the owner of the story, then include the story. If the user is not the owner and if the story is unpublished, then exclude it.

Ideally, the Django ORM should allow something like:

    queryset = queryset.filter(
        Coalesce(
            Q(owner_id=user.id),
            Q(published_date__isnull=False)
        )
    )

But upon executing, Django throws the error:

TypeError: 'Coalesce' object is not iterable

Unfortunately, I need this conditional filtering at the database level.

Is there a notation or approach that allows selection using a Coalesce expression?

I'd prefer not to use rawsql or queryset.extra.

I figured this out by myself. Since no real answer has been posted yet, here is my solution:

        return queryset.all().annotate(
            viewable=Case(
                When(owner_id=user.id, then=True),
                When(published_date__isnull=False, then=True),
                default=False,
                output_field=db.models.BooleanField()
            ),
        ).filter(
            viewable=True
        )

Fairly unreadable, isn't it? The resulting SQL is just as ugly:

AND CASE WHEN ("project_story"."owner_id" = [INSERT USER ID]) THEN True WHEN ("project_story"."published_date" IS NOT NULL) THEN True ELSE False END = True) ORDER BY "project_story"."image_count" DESC

Although using CASE leads to the same result as the original query, I'd still like less-verbose code.

Until then, I'll mark my question as answered.

Tiny beginning of code fragment for writing your own Expression class for COALESCE(), see official documentation link below for full source.

Note: not all databases have a COALESCE(), which I assume is why this isn't an official Expression already. It's probably possible to write some code which turns this into a standard Case(...) expression as a fallback, then use COALESCE() for the db specific sql conversions. I leave that as an exercise for the reader ;-)

from django.db.models import Expression

class Coalesce(Expression):
    template = 'COALESCE( %(expressions)s )'

    ...

Official documentation for the full code: Django Models - Writing your own Query Expressions

I believe the result of the Q() is always True or False, so using it in Coalesce expression does not work. The solution is to use conditional expression, combining Case and filter. You could find all information you need here in the documentation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM