go-gorm / gorm

The fantastic ORM library for Golang, aims to be developer friendly
https://gorm.io
MIT License
36.76k stars 3.93k forks source link

Nested preload with limit #5236

Open Mrbeyond opened 2 years ago

Mrbeyond commented 2 years ago

Your Question

I want to perform a limit on a nested preload to improve performance. The limit works on the total count on the children but I want the limit to work on each distinct owner.

The models are:

type User struct {
    ID                 uint64   
    Fullname           string     
    Email              string    
        Work               []Work  
}

type Work struct {
    ID           uint64       
    CompanyName  string      
    UserID       uint64       
    User         *User       //  belongs to is needed here
    Project      []Project     
}
type Project struct {
    ID          uint64        
    Role        string  
    Description string   
    WorkID      uint64       
}

Query without Limit:

db.Where("email = ?","myemail@mail.com").
        Preload("Work",  func(db *gorm.DB) *gorm.DB {
            return db.Preload("Project").
                Order("works.id DESC")
        }).
        Find(&user)

Result:

"user": {
        "fullname": "Beyond Beyond",
        "email": "mrbeyond4@gmail.com",
        "works": [
                  {
                 "company_name": "Paycoins",
                  "user": null,
                 "projects": [
                     {
                    "role": "Full stack engineer",
                     "description": "Client app for cryptocurrency payment",
                 },
                    {
                    "role": "Full stack engineer",
                     "description": "Checkout app for cryptocurrency payment",
                   },
                   {
                     "role": "Full stack engineer",
                     "description": "Web hook plugin for api integration",
                  }
                  ]
           },
               {
                  "company_name": "Medillery",
               "user": null,
                "projects": [
                     {
                     "role": "Full stack engineer",
                     "description": "Client app for creating and assigning projects",                       
                     },
                    {
                    "role": "Fronted engineer",
                        "description": "Client app for creating and assigning projects",
                    }
                    ]
           }
         ],
         }

Query used to Limit Projects assocatied with Work to just one:

db.Where("email = ?","myemail@mail.com").
        Preload("Work",  func(db *gorm.DB) *gorm.DB {
            return db.Preload("Project",  func(pjdb *gorm.DB) *gorm.DB {
                return pjdb.Limit(1).
                       Order("projects.id DESC")
                 } ).
                Order("works.id DESC")
        }).
        Find(&user)

Result:

"user": {
"user": {
        "fullname": "Beyond Beyond",
        "email": "mrbeyond4@gmail.com",
        "works": [
                  {
                 "company_name": "Paycoins",
                  "user": null,
                 "projects": [
                     {
                    "role": "Full stack engineer",
                     "description": "Client app for cryptocurrency payment",
                 },
                    {
                    "role": "Full stack engineer",
                     "description": "Checkout app for cryptocurrency payment",
                   },
                   {
                     "role": "Full stack engineer",
                     "description": "Web hook plugin for api integration",
                  }
                  ]
           },
               {
                  "company_name": "Medillery",
               "user": null,
                "projects": [  ]  ## Issue No project fetched here due to limit 1, I want a single project fetched here as well.
           }
         ],
         }

The Issue:

It limits total project fetched to just one instead one to each work.

Expected answer:

"user": {
        "fullname": "Beyond Beyond",
        "email": "mrbeyond4@gmail.com",
        "works": [
                  {
                 "company_name": "Paycoins",
                  "user": null,
                 "projects": [
                     {
                    "role": "Full stack engineer",
                     "description": "Client app for cryptocurrency payment",
                 }
                  ]
           },
               {
                  "company_name": "Medillery",
               "user": null,
                "projects": [
                     {
                     "role": "Full stack engineer",
                     "description": "Client app for creating and assigning projects",                       
                     }
                    ]
           }
         ],
         }

I seriously need help with this.

Thanks.

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has been open 360 days with no activity. Remove stale label or comment or this will be closed in 180 days

mnussbaum commented 1 year ago

This is still an active issue for me

a631807682 commented 1 year ago

First of all, before the latest version, we do not support nested preload, and secondly, the currently supported method should be

Preload("Work", func(db *gorm.DB) *gorm.DB {
                    ...
}). Preload("Work.Project")

https://github.com/go-gorm/gorm/pull/6137

cc @black-06

mnussbaum commented 1 year ago

@a631807682 thanks for the pointer to that PR! Does the new syntax allow a limit to be applied to the Project in Work.Project?

black-06 commented 1 year ago
DB.Where("email = ?", "user").
    Preload("Work").
    Preload("Work.Project", func(tx *gorm.DB) *gorm.DB {
        return tx.Limit(1).Order("projects.id DESC")
    }).
    Find(&rst).Error

SQL is

SELECT * FROM `projects` WHERE `projects`.`work_id` IN (1,2) ORDER BY projects.id DESC LIMIT 1
SELECT * FROM `works` WHERE `works`.`user_id` = 1
SELECT * FROM `users` WHERE email = "user"

But it only has one Project in all Works.


Try group by work_id:

DB.Debug().
    Where("email = ?", "user").
    Preload("Work").
    Preload("Work.Project", func(tx *gorm.DB) *gorm.DB {
        return tx.Group("work_id")
    }).
    Find(&rst).Error
// SELECT * FROM `projects` WHERE `projects`.`work_id` IN (1,2) GROUP BY `work_id`
a631807682 commented 1 year ago

It seems that we have no way to set the limit number of each subset in a query, @mnussbaum what is the raw sql you want?