使用条件查询从与组的关系中获取特定列

时间:2016-02-27 00:19:33

标签: grails gorm hibernate-criteria

我在制定条件查询时遇到问题。

我正在寻找特定用户的明显季节(身份证,姓名),联盟(身份证,姓名),以便我可以将其变成缩进列表:

  • SeasonA
    • LeagueA
    • LeagueB
    • ...
  • SeasonB
    • LeagueA
    • LeagueC
    • ...

域类:

class League {

    String name
    User user

    static hasMany = [games:Game]  
}

class Season {

    String name
    User user

    static hasMany = [games:Game]
}

class Game {

    Season season
    League league
}

class User {

    String username
    String password
    String email
}

标准查询:

def id = 1
def seasonList = Game.createCriteria().list{
    season{
        projections{
            property('id')
            property('name')
            groupProperty('id')
        }
        user{
            eq 'id', id.toLong()
        }
    }
    league{
        projections{
            property('id')
            property('name')
            groupProperty('id')
        }
    }
}   

结果SQL:

select
    league_ali3_.id as y0_,
    league_ali3_.name as y1_,
    league_ali3_.id as y2_
from
    game this_
inner join
    league league_ali3_
        on this_.league_id=league_ali3_.id
inner join
    season season_ali1_
        on this_.season_id=season_ali1_.id
inner join
    user user_alias2_
        on season_ali1_.user_id=user_alias2_.id
where
    (
        (
            user_alias2_.id=?
        )
    )
group by
    league_ali3_.id

我可以看到它错过了季节属性,也没有按季节分组。

我注意到最后一个闭包似乎是它会选择的。如果我改变赛季和联赛关闭,我会选择赛季场地。

我决定尝试别名:

def seasonList = Game.createCriteria().list{
    createAlias('season','s')
    createAlias('league','l')

    season{
        projections{
            property('s.id')
            property('s.name')
            groupProperty('s.id')
        }
        user{
            eq 'id', id.toLong()
        }
    }
    league{
        projections{
            property('l.id')
            property('l.name')
            groupProperty('l.id')
        }
    }   
}

但是,我得到QueryException: duplicate association path: season。同样,第一个闭包将会引发错误。

由于我甚至没有重复,我还没有解决我的查询的不同部分。

更新#2

最终标准查询,添加了分组:

def seasonList = Game.createCriteria().list{
            resultTransformer = new org.hibernate.transform.AliasToEntityMapResultTransformer()

            projections {
                season {
                    property('id', 'seasonId')
                    property('name', 'seasonName')
                    groupProperty('id')
                }

                league {
                    property('id', 'leagueId')
                    property('name', 'leagueName')
                    groupProperty('id')
                }
            }

            season { 
                user {
                    eq 'id', id.toLong()
                } 
            }
        }

       def seasons = seasonList
        .groupBy { it.seasonId }
        .collect { seasonId, records -> 
            [
                id: seasonId, 
                name: records.head().seasonName, 
                leagues: records.collect { [id: it.leagueId, name: it.leagueName] }
            ]
        }

1 个答案:

答案 0 :(得分:1)

问题在于您已经向后推出了投影层次结构。您需要从projections开始,然后进入协会:

def id = 1
def seasonList = Game.createCriteria().list{
    projections {
        season {
            property('id')
            property('name')
        }

        league {
            property('id')
            property('name')
        }
    }

    season { 
        user {
            eq 'id', id.toLong()
        } 
    }
} 

您未使用汇总功能,因此您不需要groupProperty()

规范化结果

上面的查询返回一个平面列表,这对创建缩进列表提出了挑战;您必须迭代并跟踪会话何时更改。我们可以做得更好。我们首先将结果从List<List>更改为List<Map>

def id = 1
def seasonList = Game.createCriteria().list{
    resultTransformer = new org.hibernate.transform.AliasToEntityMapResultTransformer()

    projections {
        season {
            property('id', 'seasonId')
            property('name', 'seasonName')
        }

        league {
            property('id', 'leagueId')
            property('name', 'leagueName')
        }
    }

    season { 
        user {
            eq 'id', id.toLong()
        } 
    }
} 

映射结果转换器(在本例中是属性别名)使得查询返回如下内容:

[
    [seasonId: 1, seasonName: 'Season A', leagueId: 100, leagueName: 'League A'],
    [seasonId: 1, seasonName: 'Season A', leagueId: 200, leagueName: 'League B'],
    [seasonId: 2, seasonName: 'Season B', leagueId: 100, leagueName: 'League A'],
    [seasonId: 2, seasonName: 'Season B', leagueId: 300, leagueName: 'League C']
]

到目前为止,结果中唯一的变化是您现在拥有可用于引用数据的密钥。这在大脑上比数组索引容易得多。数据仍然是平坦的(非规范化),但这很容易处理:

def seasons = seasonList
    .groupBy { it.seasonId }
    .collect { seasonId, records -> 
        [
            id: seasonId, 
            name: records.head().seasonName, 
            leagues: records.collect { [id: it.leagueId, name: it.leagueName] }
        ]
    }

这是结果:

[
    [
        'id':1, 'name':'Season A', 'leagues':[
            ['id':100, 'name':'League A'], 
            ['id':200, 'name':'League B']
        ]
    ], 
    [
        'id':2, 'name':'Season B', 'leagues':[
            ['id':100, 'name':'League A'], 
            ['id':300, 'name':'League C']
        ]
    ]
]

现在,您可以轻松使用嵌套数据来创建缩进列表。这是普通Groovy中的一个例子,可以很容易地适应GSP:

seasons.each { season ->
    println "$season.name"
    season.leagues.each { league -> println "\t$league.name" }
}

以上示例打印以下内容:

Season A
    League A
    League B
Season B
    League A
    League C

考虑正常化

通过将一个方法分解并传入数据和配置,可以使功能更具可重用性:

def normalize(List rows, Map config) {
    rows.groupBy { it[config.id] }
    .collect { id, records -> 
        [
            id: id, 
            name: records.head()[config.name], 
            (config.children.key): records.collect { [id: it[config.children.id], name: it[config.children.name]] }
        ]
    }
}

然后,您可以使用查询输出和Map告诉方法使用哪个属性来调用该方法。

def seasons = normalize(seasonList, [id: 'seasonId', name: 'seasonName', children: [key: 'leagues', id: 'leagueId', name: 'leagueName']])