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

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

因此,到目前为止,我只能在本机SQL查询中找到在Grails中联接这些不相关表的唯一方法。 HQL中的左联接需要在表中定义一个关系(例如left join checkoutJournal.checkoutDate,这需要checkoutJournal中的RentalCar字段,据我所知,它将在数据库表中添加额外的列)

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

   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
  • 在不更改数据库的情况下实现查询的最佳方法是什么
    方案?

  • 我正在考虑将RentalCar子类化(以将组成的实体与基本实体分离),并与CheckoutJournal和RepairJournal的其他关系添加类似以下内容:
      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返回的数组?

  • 非常感谢,非常感谢您的帮助。

    亲切的问候

    最佳答案

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

    问题

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

    我从未尝试使用子类来解决此类问题,但我认为这行不通的原因之一是GORM / Hibernate无法表示您在checkout_journal.checkout_date上使用的复杂的左外部联接。

    可能的解决方案

    您也许可以使用数据库 View 来模拟创建GORM关联所必需的映射表。另一个开发人员对此具有some success。有了适当的关联,您就可以使用HQL或条件查询。

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

    RentalCar到CheckoutJournal的映射

    从以下 View 开始(考虑它们为伪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
    

    使用适当的 View ,您可以在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
            ]
        ]
    ]
    

    09-26 23:05