使用条件查询从与组的关系中获取特定列
Getting specific columns from relationships with group by using criteria query
我在制定条件查询时遇到问题。
我正在考虑为特定用户获取不同的 Season(id, name)、League(id, name),以便我可以将其转换为缩进列表:
- 第A季
- 联赛A
- 乙级联赛
- ...
- 第二季
- 联赛A
- 联赛C
- ...
域类:
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
我可以看到它缺少季节属性,也没有按季节分组。
我注意到最后一个闭包似乎是它的样子 select。如果我切换赛季和联赛关闭,我将获得赛季字段。
我决定试试别名:
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] }
]
}
一个问题是您的投影层次结构倒退了。您需要从 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()
。
规范化结果
上面的查询 return 是一个平面列表,因此创建缩进列表是一个挑战;您必须迭代并跟踪会话何时更改。我们可以做得更好。让我们首先将结果从 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()
}
}
}
地图结果转换器,在本例中连同 属性 别名,使查询 return 像这样:
[
[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 来调用该方法,该 Map 告诉该方法要使用哪个 属性。
def seasons = normalize(seasonList, [id: 'seasonId', name: 'seasonName', children: [key: 'leagues', id: 'leagueId', name: 'leagueName']])
我在制定条件查询时遇到问题。
我正在考虑为特定用户获取不同的 Season(id, name)、League(id, name),以便我可以将其转换为缩进列表:
- 第A季
- 联赛A
- 乙级联赛
- ...
- 第二季
- 联赛A
- 联赛C
- ...
域类:
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
我可以看到它缺少季节属性,也没有按季节分组。
我注意到最后一个闭包似乎是它的样子 select。如果我切换赛季和联赛关闭,我将获得赛季字段。
我决定试试别名:
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] }
]
}
一个问题是您的投影层次结构倒退了。您需要从 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()
。
规范化结果
上面的查询 return 是一个平面列表,因此创建缩进列表是一个挑战;您必须迭代并跟踪会话何时更改。我们可以做得更好。让我们首先将结果从 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()
}
}
}
地图结果转换器,在本例中连同 属性 别名,使查询 return 像这样:
[
[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 来调用该方法,该 Map 告诉该方法要使用哪个 属性。
def seasons = normalize(seasonList, [id: 'seasonId', name: 'seasonName', children: [key: 'leagues', id: 'leagueId', name: 'leagueName']])