Skip to content

Annotating queries

Annotating queries is a bit difficult in edgy because we use a hash function with a prefix (select_related) path to generate stable names for joins. But given you mostly use just a subquery you won't run often in this case.

Annotating a child with the parent columns

We have embed target for drilling down to a child element but sometimes lack parameters of the parent. Here comes the function reference_select to help. This works analogue to django's F() function but is more generic.

import sqlalchemy

import edgy

models = edgy.Registry(database=...)


class User(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Profile(edgy.Model):
    user = edgy.fields.OneToOne(User, related_name="profile")
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


for profile in await Profile.query.select_related("profile").reference_select(
    {"user": {"profile_name": "name"}}
):
    assert profile.user.profile_name == profile.name

# up to one level you can leave out the select_related()
# you can also reference columns in case you use them of the main table or explicitly provided via extra_select

for profile in await Profile.query.reference_select(
    {"user": {"profile_name": Profile.table.c.name}}
):
    assert profile.user.profile_name == profile.name
import sqlalchemy

import edgy

models = edgy.Registry(database=...)


class User(edgy.StrictModel):
    name = edgy.CharField(max_length=100)
    profile_name = edgy.fields.PlaceholderField(null=True)

    class Meta:
        registry = models


class Profile(edgy.Model):
    user = edgy.fields.OneToOne(User, related_name="profile")
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


for profile in await Profile.query.select_related("profile").reference_select(
    {"user": {"profile_name": "name"}}
):
    assert profile.user.profile_name == profile.name

# up to one level you can leave out the select_related()

for profile in await Profile.query.reference_select({"user": {"profile_name": "name"}}):
    assert profile.user.profile_name == profile.name

Warning

In case you use the StrictModel every name where the query result is attached must be a field. You can use PlaceHolderField(null=True) for reserving an attribute name for assignment.

Annotating and embed_parent

reference_select is executed before embedding. This can be helpful to add some parent attributes to the grand child.

import sqlalchemy

import edgy

models = edgy.Registry(database=...)


class User(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Profile(edgy.Model):
    user = edgy.fields.OneToOne(User, related_name="profile")
    name = edgy.CharField(max_length=100)
    profile = edgy.fields.OneToOne(
        "SuperProfile", related_name="profile", embed_parent=("user", "normal_profile")
    )

    class Meta:
        registry = models


class SuperProfile(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


for profile in await SuperProfile.query.all():
    user = (
        await profile.profile.select_related("user")
        .reference_select({"user": {"profile_name": "name"}})
        .get()
    )
    assert isinstance(user, User)
    assert user.normal_profile.name == user.profile_name

Annotating child attributes to the parent

reference_select point of view is always the main query. This means you can set child attributes via

reference_select({"user": {"user_name", "user__name"}}) to the child itself.

Or you can do all manually by using the helper function hash_tablekey (not recommended).

import sqlalchemy

import edgy
from edgy.core.utils.db import hash_tablekey

models = edgy.Registry(database=...)


class User(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Profile(edgy.Model):
    user = edgy.fields.OneToOne(User, related_name="profile")
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


for profile in await Profile.query.select_related("user").reference_select(
    {"user_name": sqlalchemy.text(f"user__name")}
):
    assert profile.user_name == profile.user.name


# manual way
join_table_key = hash_tablekey(tablekey=User.table.key, prefix="user")
for profile in await Profile.query.select_related("user").reference_select(
    {"user_name": sqlalchemy.text(f"{join_table_key}_name")}
):
    assert profile.user_name == profile.user.name

Annotating and subqueries

Until now we referenced only child parent structures but we can also reference arbitary select statements. How to insert some? Using extra_select. You can add as many as you want and label them as you want but are responsible for collisions.

import sqlalchemy
from sqlalchemy import func

import edgy

models = edgy.Registry(database=...)


class User(edgy.Model):
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


class Profile(edgy.Model):
    user = edgy.fields.OneToOne(User, related_name="profile")
    name = edgy.CharField(max_length=100)

    class Meta:
        registry = models


for profile in await Profile.query.extra_select(
    func.count()
    .select()
    .select_from((await User.query.as_select()).subquery())
    .label("total_number")
).reference_select({"total_number": "total_number"}):
    assert profile.total_number == 10


# or manually
for profile in await Profile.query.extra_select(
    sqlalchemy.select(func.count(User.table.c.id).label("total_number")).subquery()
).reference_select({"total_number": "total_number"}):
    assert profile.total_number >= 0

The cool thing is you are not limited to subqueries. CTEs can be referenced too, you just need to know the name or to provide the column.