【问题标题】:Left joining unrelated entities with Grails / Hibernate使用 Grails / Hibernate 加入不相关的实体
【发布时间】:2016-02-04 05:44:49
【问题描述】:

我正在使用 Grails 访问一个共享数据库,该数据库由另一个应用程序的 ORM 创建并填充,并且希望在不修改底层表结构的情况下在其上执行 LEFT JOIN 和聚合函数。理想情况下,这应该发生在高级别的基础上,例如使用 HQL、Criteria 或类似方式。

这些表不相关,它们没有由外键定义的关系。这些表使用可用于执行连接的字符串标识符系统。

因此,到目前为止,我能找到在 Grails 中连接这些不相关表的唯一方法是使用原生 SQL 查询。 HQL 中的左连接需要表中定义的关系(例如,left join checkoutJournal.checkoutDate 需要 RentalCar 中的 checkoutJournal 字段,据我了解,这会在数据库表中添加额外的列)

我的域类看起来像这样 - 他们目前唯一做的就是表示数据库的表结构。

   class RentalCar {
     String identifier
     static mapping = {
       table 'rental_car'
     }
     ...
   }

   class CheckoutJournal {
     String identifier
     String renterName
     Date checkoutDate
     static mapping = {
       table 'checkout_journal'
     }
     ...
   } 

   class RepairJournal {
     String identifier
     String repairDescription
     Date repairDate
     static mapping = {
       table 'repair_journal'
     }
     ...
   }

我要构建的结果数据集应该具有这种结构(以 JSON 表示法):

[
    {
       identifier: "xyz",
       // = max checkout date from CheckoutJournal, identifier String NOT unique
       lastCheckoutDate: "2015-10-31 20:06:17.738831",
       // all repairDate entries from RepairJournal that match identifier,
       // realized with aggregate function in SQL
       repairHistory: [
          "2015-10-31 20:06:17.738831",
          "2015-10-30 20:06:17.738831",
          "2015-10-29 20:06:17.738831"
       ] 
   }, {
     // Another rental car with the same structure
      ...
}]

我对所需结果的原生 SQL 查询如下所示:

SELECT
  rc.identifier,
  coj.renterName,
  array_agg(rej.repairDate)

FROM rental_car rc

LEFT JOIN checkout_journal AS coj
  ON coj.identifier = rc.identifier 
  AND coj.checkout_date =
    ( SELECT max(checkout_date)
      FROM checkout_journal c
      WHERE rc.identifier = c.identifier )

LEFT JOIN repair_journal AS rej
  ON rc.identifier = rej.identifier 

GROUP BY rc.identifier, coj.renter_name
  • 在不更改数据库的情况下实现我的查询的最佳方法是什么 方案?

我正在考虑使用与 CheckoutJournal 和 RepairJournal 的附加关系来子类化 RentalCar(将组合实体与基本实体分开)添加类似于

  static mapping = {
            checkoutJournal indexColumn: [name: "identifier", type: String],
                              joinTable: [column: "identifier"],
            repairJournal indexColumn: [name: "identifier", type: String], 
                              joinTable: [column: "identifier"]

加上一个标准

...
    projections {
        max "checkoutDate"
    }
...
  • 这是正确的方法并且不会改变表结构吗?
  • 如何使用条件查询我的数据,表示lastCheckoutDate 的最大聚合以及我希望为repairHistory 返回的数组?

非常感谢,非常感谢任何帮助。

亲切的问候

【问题讨论】:

    标签: hibernate postgresql grails join hql


    【解决方案1】:

    我将专注于主要问题,即如何加入不相关的实体/域类。

    问题

    不幸的是,正如您所发现的,GORM/Hibernate 连接需要关联属性,这会导致将列添加到域类表中。如果没有关联,则无法进行连接。这适用于 HQL、条件查询和 where 查询。我有一个关于这个主题的article,你可能会觉得有帮助。

    我从未尝试使用子类化来解决这样的问题,但我认为它不会起作用的一个原因是 GORM/Hibernate 不能表示您在 checkout_journal.checkout_date 上使用的复杂左外连接。

    可能的解决方案

    您可以使用数据库视图来模拟创建 GORM 关联所必需的映射。另一位开发人员对此有some success。通过适当的关联,您将能够使用 HQL 或条件查询。

    注意:查询仅支持内部联接。

    从 RentalCar 到 CheckoutJournal 的映射

    从以下视图开始(将它们视为伪 SQL):

    -- This view transforms the checkout_journal table into something GORM compliant by creating a unique key for each journal entry
    CREATE VIEW checkout_journal_gorm AS
    SELECT CONCAT(identifier, to_char(checkout_date, '-YYYY-MM-DD-HH-MI-SS-US')) as checkout_identifier, renter_name, checkout_date
    FROM checkout_journal
    
    -- This view makes it possible to create a one-to-many association from RentalCar to CheckoutJournal.
    CREATE VIEW rental_car_checkous AS 
    SELECT rc.identifier AS rental_car_identifier, CONCAT(coj.identifier, to_char(coj.checkout_date, '-YYYY-MM-DD-HH-MI-SS-US')) as checkout_identifier
    FROM rental_car AS rc INNER JOIN checkout_journal as coj ON rc.indentifier = coj.identifier
    

    有了视图,您可以在RentalCarCheckoutJournal 域类中使用它们。

    class RentalCar {
        String identifier
    
        static hasMany = [
            checkouts: CheckoutJournal,
            repairs: RepairJournal
        ]
    
        static mapping = {
            table 'rental_car'
            checkouts joinTable: [
                name: 'rental_car_checkous',
                key: 'rental_car_identifier',
                column: 'checkout_identifier'
            ]
        }
        ...
    }
    
    class CheckoutJournal {
        String identifier
        String renterName
        Date checkoutDate
    
        static mapping = {
            table 'checkout_journal_gorm'
            id column: 'checkout_identifier', name: 'identifier'
        }
        ...
    } 
    

    然后您可以对RepairJournal 执行等效操作。

    GORM 查询

    通过设置符合 GORM 的域类(顺便说一句,现在是只读的),您可以使用 HQL 或条件查询来代替 SQL。这是一个例子:

    RentalCar.withCriteria {
        resultTransformer = new org.hibernate.transform.AliasToEntityMapResultTransformer()
        createAlias 'checkouts', 'c', org.hibernate.sql.JoinType.LEFT_OUTER_JOIN
        createAlias 'repairs', 'r', org.hibernate.sql.JoinType.LEFT_OUTER_JOIN
    
        projections {
            groupProperty 'identifier', 'identifier'
            max 'c.checkoutDate', 'lastCheckoutDate'
            groupProperty 'r.repairDate', 'repairDate'
        }
    }
    

    输出如下所示:

    [
        [identifier: 'xyz', lastCheckoutDate: '2015-10-31 20:06:17.738831', repairDate: '2015-10-31 20:06:17.738831'],
        [identifier: 'xyz', lastCheckoutDate: '2015-10-31 20:06:17.738831', repairDate: '2015-10-30 20:06:17.738831'],
        [identifier: 'xyz', lastCheckoutDate: '2015-10-31 20:06:17.738831', repairDate: '2015-10-29 20:06:17.738831']
    ]
    

    这样的输出可以这样转换:

    rows.groupBy { it.identifier }.collect { identifier, maps -> 
        [
            identifier: identifier, 
            lastCheckoutDate: maps[0].lastCheckoutDate,
            repairHistory: maps*.repairDate
        ]
    }
    

    要生成您正在寻找的结构:

    [
        [
            identifier:xyz, 
            lastCheckoutDate:2015-10-31 20:06:17.738831, 
            repairHistory:[
                2015-10-31 20:06:17.738831, 
                2015-10-30 20:06:17.738831, 
                2015-10-29 20:06:17.738831
            ]
        ]
    ]
    

    【讨论】:

    • 感谢您的快速回答。听起来是一个非常直接的方法。我喜欢输出转换。将尽快尝试并更新结果。
    • 我的主要问题是在 RentalCar 的映射中配置 joinTable。目前我被以下问题所困扰:不会键指定本地字段(在表rental_car中)和列外部字段(在checkout_journal_gorm中)-参见示例grails.github.io/grails-doc/3.0.x/ref/Database%20Mapping/…。还是rental_car中的两个字段?我不清楚 key 和 inverse 列的确切含义。
    • 是的,这令人困惑。首先,我忘记设置 RentalCar 的 id:id generator: 'assigned', name: 'identifier'。键表示 RentalCar 实例的 id 将在rental_car_identifier 列中。 Column 表示 CheckoutJournal 的 Id 将在 checkout_identifier 列中。
    • 该问题目前处于搁置状态,您答案中的概念肯定会导致解决方案并使我摆脱困境,这就是我将其标记为正确的原因。我在标准查询的语义上有一些细微的差异,并将在一两个月内发布对我有用的内容,也许还有一些额外的结论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-20
    • 2015-01-15
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-18
    相关资源
    最近更新 更多