question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question — Is this the right way for adding a field that depend on a relationship?

See original GitHub issue

I have a naive question but I don’t know the words to search for. I’m sure this has been asked one million times but I’m clueless.

I have some blog posts with likes on them and I would like to query a list of blog posts with a flag on each post to tell wether the given user liked the post or not.

My current solution:

  • I added a field in my schema (under schema.Blog.has_user_liked)
  • I added lazy="dynamic" on models.Blog.likes so I can filter likes for a particular user

I wonder if this is the right approach as it makes “a lot” of disjoint SQL requests (at least one to get the blog posts and one per blog post). It sounds like a lot to me, but as I understand there is a compromise here because this will allow to only retrieve some blog posts (not ALL) and still have decent performances since it will not join very large tables. Note that I never plan on using this has_user_liked field anywhere else but in a web view with pagination.

Is this the “right” solution? Is there a better way to achieve this? I would really appreciate any comment/critic about this.

Details about this solution

My model looks like this:

class User(database.Base):
    user_id = IDColumn()

class Blog(database.Base):
    blog_id = IDColumn()
    likes = relationship("Like", back_populates="blog", lazy="dynamic")

class Like(database.Base):
    blog_id = IDColumn(ForeignKey(Blog.blog_id))
    user_id = IDColumn(ForeignKey(User.user_id))
    blog = relationship("Blog", back_populates="likes")

My schema looks like this:

class User(SQLAlchemyObjectType):
    class Meta:
        model = models.User
        interfaces = (Node,)

class Blog(SQLAlchemyObjectType):
    class Meta:
        model = models.Blog
        interfaces = (Node,)

    has_user_liked = graphene.Field(
        graphene.Boolean, user_id=graphene.String(required=True)
    )

    def resolve_has_user_liked(self: models.Blog, info, user_id):
        return self.likes.filter(user_id == user_id).count()

class Like(SQLAlchemyObjectType):
    class Meta:
        model = models.Like
        interfaces = (Node,)


class Query(graphene.ObjectType):
    node = Node.Field()
    blogs = SQLAlchemyConnectionField(Blog)

If I make a request like this one:

query Test($userId: String!) {
    blogs{
        edges {
            node {
                id
                hasUserLiked(userId: $userId)
            }
        }
    }
}

I get the expected result (in my test DB I have 3 posts, one of which has one like from the given user):

{
    "data": {
        "blogs": {
            "edges": [
                {
                    "node": {
                        "id": "Blog:fdb231cd-2a19-4d1f-9443-5717f44275af",
                        "hasUserLiked": false
                    }
                },
                {
                    "node": {
                        "id": "Blog:375afe8e-a52a-4098-98b7-f04a4b504097",
                        "hasUserLiked": false
                    }
                },
                {
                    "node": {
                        "id": "Blog:01580847-bb33-40ef-93e4-8025a1e31145",
                        "hasUserLiked": true
                    }
                }
            ]
        }
    }
}

The “problem” or rather my question concerns the generated SQL queries, from what I can see in SQLAlchemy’s log, here is what the requests are:

SELECT count(*) AS count_1 
FROM (SELECT "user".blog.blog_id AS user_blog_blog_id, "user".blog.author AS user_blog_author, "user".blog.date AS user_blog_date, "user".blog.title AS user_blog_title, "user".blog.content AS user_blog_content, "user".blog.image AS user_blog_image, "user".blog.avatar AS user_blog_avatar 
FROM "user".blog) AS anon_1

SELECT count(*) AS count_1 
FROM (SELECT "user".blog.blog_id AS user_blog_blog_id, "user".blog.author AS user_blog_author, "user".blog.date AS user_blog_date, "user".blog.title AS user_blog_title, "user".blog.content AS user_blog_content, "user".blog.image AS user_blog_image, "user".blog.avatar AS user_blog_avatar 
FROM "user".blog) AS anon_1

SELECT "user".blog.blog_id AS user_blog_blog_id, "user".blog.author AS user_blog_author, "user".blog.date AS user_blog_date, "user".blog.title AS user_blog_title, "user".blog.content AS user_blog_content, "user".blog.image AS user_blog_image, "user".blog.avatar AS user_blog_avatar 
FROM "user".blog 
 LIMIT %(param_1)s

SELECT "user".blog.blog_id AS user_blog_blog_id, "user".blog.author AS user_blog_author, "user".blog.date AS user_blog_date, "user".blog.title AS user_blog_title, "user".blog.content AS user_blog_content, "user".blog.image AS user_blog_image, "user".blog.avatar AS user_blog_avatar 
FROM "user".blog 
 LIMIT %(param_1)s

-- User's likes

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

-- {'param_1': UUID('fdb231cd-2a19-4d1f-9443-5717f44275af')}

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

-- {'param_1': UUID('375afe8e-a52a-4098-98b7-f04a4b504097')}

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

SELECT count(*) AS count_1 
FROM (SELECT "user"."like".like_id AS user_like_like_id, "user"."like".blog_id AS user_like_blog_id, "user"."like".user_id AS user_like_user_id 
FROM "user"."like" 
WHERE %(param_1)s = "user"."like".blog_id) AS anon_1

-- {'param_1': UUID('01580847-bb33-40ef-93e4-8025a1e31145')}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
chrisberkscommented, Mar 16, 2021

I forgot to point out the benefit of using the Dataloader.

By using the Dataloader there will only be two SQL queries, one to get the blogs and one to resolve the hasUserLiked field for every blog—however many there are.

As you’re using Starlette you may want to use aiodataloader (Asyncio DataLoader).

If you’re not familiar with Dataloaders I’d recommend taking a look at the README of the JavaScript/reference implementation of Dataloader. The Dataloader video from Lee Byron, linked at the bottom of the README, is also super helpful.

The README of aiodataloader is the same, I think, but with Python code examples. The Using with GraphQL section should explain what’s going on in the code example I gave.

0reactions
erikwredecommented, May 15, 2022

I’m closing this since everything seems to be resolved. If there’s any remaining questions, feel free to reply or open a new issue.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Create, edit or delete a relationship - Microsoft Support
In the Relationships window, add the tables that you want to relate, and then drag the field to relate them from one table...
Read more >
Chapter 8 The Entity Relationship Data Model
Entity, Entity Set and Entity Type ... Entities can be classified based on their strength. An entity is considered weak if its tables...
Read more >
Questions about Relationships, the Data Model, and Data ...
Is a relationship just a different name for a join? Relationships are a dynamic, flexible way to combine data from multiple tables for...
Read more >
The 6 Types of Relationships in Salesforce
Lookup fields are not required on the page layout of the detail record but if you make them a required field, it is...
Read more >
Considerations for Relationships - Salesforce Help
You can convert a master-detail relationship to a lookup relationship as long as no roll-up summary fields exist on the master object. Converting...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found