gorm icon indicating copy to clipboard operation
gorm copied to clipboard

How to join nested tables

Open sintanial opened this issue 2 years ago • 5 comments

Your Question

I have models like this:

package models

type ItemAsset struct {
	ID int32 `gorm:"primary_key;AUTO_INCREMENT;column:id;type:int;" json:"id"`
	ItemID string `gorm:"column:item_id;type:varchar;size:36;" json:"item_id"`
}

type ItemSet struct {
	ID uint32 `gorm:"primary_key;AUTO_INCREMENT;column:id;type:uint;" json:"id"`
}

type Item struct {
	ID int32 `gorm:"primary_key;AUTO_INCREMENT;column:id;type:int;" json:"id"`
	ItemID string `gorm:"column:item_id;type:varchar;size:36;" json:"item_id"`
	SetID null.Int `gorm:"column:set_id;type:uint;" json:"set_id"`
}

type PlayerItem struct {
	ID int32 `gorm:"primary_key;AUTO_INCREMENT;column:id;type:int;" json:"id"`
	PlayerID string `gorm:"column:player_id;type:varchar;size:128;" json:"player_id"`
	ItemID string `gorm:"column:item_id;type:varchar;size:36;" json:"item_id"`
	ItemLevelID null.Int `gorm:"column:item_level_id;type:uint;" json:"item_level_id"`
}

type ItemLevel struct {
	ID uint32 `gorm:"primary_key;AUTO_INCREMENT;column:id;type:uint;" json:"id"`
	ItemID string `gorm:"column:item_id;type:varchar;size:36;" json:"item_id"`
}

And then i have another structure which combine all structure with relations

package entity

type Item struct {
	models.Item
	ItemAsset *models.ItemAsset `gorm:"foreignKey:ItemID;references:item_id" json:"item_asset"`
	ItemSet   *models.ItemSet          `gorm:"foreignKey:ID;references:set_id" json:"item_set"`
}

type PlayerItem struct {
	models.PlayerItem
	Item      Item       `gorm:"foreignKey:ItemID;references:item_id" json:"item"`
	ItemLevel *models.ItemLevel `gorm:"foreignKey:ItemLevelID;references:id" json:"item_level"`
}

So my question is, how can i join nested models when i fetch entity.PlayerItem ?

Query like this is not working :(

db.
		Joins("ItemLevel").
		Joins("Item").
		Joins("Item.ItemAsset").
		Joins("Item.ItemSet").
		Find(&items).

Yes, of course i know about Preload ! But it's generate separate query, but i want to join nested structure by LEFT JOIN query !

sintanial avatar Apr 21 '22 02:04 sintanial

I also needing the same feature. A simple Joins("Item.ItemAsset") like the Preload, but using LEFT JOIN

lisandromdc avatar May 24 '22 15:05 lisandromdc

+1. i also

0xdeface avatar Jun 23 '22 02:06 0xdeface

JOIN no work for me ._.

lfelguetac avatar Jul 07 '22 22:07 lfelguetac

Hate to be the "me too" guy... but me too :(

d-baranowski avatar Jul 12 '22 22:07 d-baranowski

Try by adding tag gorm:"embedded":

type PlayerItem struct {
	models.PlayerItem `gorm:"embedded"`
	Item      Item       `gorm:"foreignKey:ItemID;references:item_id" json:"item"`
	ItemLevel *models.ItemLevel `gorm:"foreignKey:ItemLevelID;references:id" json:"item_level"`
}

But it doesn't work in the way you want to. It will create model.PlayerItem fields in PlayerItem structure. So now when you will try to do INSERT on PlayerItem struct it will demand model.PlayerItem fields and so on... It really need improvement.

Or you can try creating lets call it aggregator. When you are doing more than one Join on similar fields like IDs gorm.io will return two fields called ID (ItemAsset and Item) not itemAsset.ID item.ID.

So what can you do is to create this aggregator.

func gatherResult() {
  type aggregator struct {
    item Item `gorm:"embedded"`
    itemLevel ItemLevel `gorm:"embedded;embeddedPrefix:itemlevel_"`
  }
  var item []Item
  var result []aggregator
  db.Join("LEFT JOIN itemLevel on itemLevel.id = item.itemLevelID").Select([]string{"item.id","item.itemID","itemLevel.ID as itemlevel_id","itemLevel.itemID as itemlevel_item_id"}).Model(&item).Scan(&result)
}

Just adjust it to your code.

MateuszKepczynski avatar Jul 18 '22 14:07 MateuszKepczynski

I believe you can use .Preload("Item.ItemAsset") if you want to load it. At least that works for me, maybe you should try that.

TobiasGrether avatar Jan 11 '23 17:01 TobiasGrether

Having the same issue. embedded tag is not a workaround for this, you can not aggregate multiple separate tables into a single one and call it a solution, this will come with a lot of drawbacks and is not how relational databases are desired to be used.

Preload is also no viable option considering it's performance difference especially for large data sets.

Please make it possible to use nested joins, it is a very basic SQL feature that I'd expect from any ORM to support.

CubicrootXYZ avatar Feb 15 '23 12:02 CubicrootXYZ

This is definatly a necessary feature for some users. We started using gorm, but recently discovered that there is no possibility to use JOIN with 1:N relations.

This is a hard drawback, hopefully there is some feedback if this is getting implemented in the future :-)

4ND3R50N avatar Feb 15 '23 12:02 4ND3R50N

When will this feature release?

KiddoV avatar Mar 17 '23 13:03 KiddoV

why cant we just preload and join the same table?? Preload the nested data we want to get, and then use Join for conditions

bcbill avatar Apr 18 '23 09:04 bcbill

When will this feature release?

@KiddoV already released last week

bcbill avatar Apr 18 '23 10:04 bcbill

I also need this feature. Thank god it is released. Unable to find any documentation for it. Can someone please mention the link to it? Thanks.

amirejaz75 avatar Apr 18 '23 18:04 amirejaz75

What version is the latest for Gorm? Kinda confused the latest one was released 2 years ago 🤔

KiddoV avatar Apr 18 '23 23:04 KiddoV

There is no documentation at present, you can refer to unit test, I will add it to the documentation this month, if anyone is willing to help add documentation, please commented in https://github.com/go-gorm/gorm/issues/6226

The latest version is v1.25.0

a631807682 avatar Apr 19 '23 04:04 a631807682

Thanks @a631807682 . I tried and it's working for nested structs but only where there is 1:1 relationship. Like

        DB.
	Joins("Manager").
	Joins("Manager.Company").
	Joins("Manager.NamedPet").
	Joins("NamedPet").
	Joins("NamedPet.Toy")

All these relationships are 1:1, i.e Manager has only one company, NamedPet etc.

But it is failing where we have 1:N relationship. i.e it will fail in the following scenario (as Manager/User has 1:N relationship with Pets):

        DB.
	Joins("Manager").
	Joins("Manager.Pets")

OR

        DB.
        Joins("User.Pets")

How we can use nested joins with 1:N relationships? Is there any way other than Preload? Though It works well with Preload, but it will cost network latency as it creates separate queries (for 1:N relationship it makes almost 4 to 5 queries).

amirejaz75 avatar Apr 19 '23 06:04 amirejaz75

Join will not handle 1:N relationship.

  1. Join from native sql will not handle 1:N relationship. When API and sql have the same syntax, we must ensure that their meanings are also similar.
  2. When using Join, we cannot actually determine the ownership of the collection. Take the pseudo code below as an example. If there are two users with different ids but the same name, how do we determine which user the pet belongs to?
DB.Select("User.Name, Pets.Name").Joins("Pets").Find(&user)

a631807682 avatar Apr 19 '23 06:04 a631807682

@a631807682

Nested Join is working for this: DB.Joins("Manager").Joins("Manager.Pet")

It works for Preload, but not working for Nested Join: DB.Joins("Manager").Joins("Manager.Pet").Joins("Manager.Pet.Toy")

It shows this error on the following line: Error 1054 (42S22): Unknown column 'Pet.toy_id' in 'on clause' LEFT JOIN toys Pet__Toy ON Pet.toy_id = Pet__Toy.id

The generated query should be like this, wrong alias: LEFT JOIN toys Pet__Toy ON Manager__Pet.toy_id = Pet__Toy.id

Manager.Pet.Toy Manager can have a Pet, and that Pet can Have a Toy. Any workaround for this?

selopia909 avatar Apr 25 '23 03:04 selopia909