2016-08-09 115 views
0

我需要在下面生成預期結果。基本上,這是一個按照特定時期彙總值的查詢(每週,每月等)。有一個日期過濾器,開始和結束,我們需要返回所有範圍的值。如果它們不存在,則返回0.處理JPA期間查詢的正確方法是什麼?

在下面的示例中,開始日期是'2015-08-02',日期'2015-08-23'且週期爲每週。請注意,對於第2周,我們沒有值,但應該返回零值。

因此,在這種情況下,使用JPA做到這一點的最佳方法是什麼?我們考慮使用臨時表並將結果與​​此表結合以獲得整個範圍的結果,但我不知道是否可以使用JPA,因爲我們需要創建表,加入並銷燬臨時表。

另一種選擇是創建數據庫視圖並將其映射到實體。

在上述情況下,JPQL查詢應該是這樣的:

@Query("select status, sum(totalInvoice), week from Invoice where " + 
     "left join TempTable as tt..." + <-- TEMP TABLE OR VIEW TO GET THE PERIODS 
     "issuer.id = :issuerId and type = :type and (:recipientId is null or recipient.id = :recipientId) and " + 
     "status in ('ISSUED', 'PAID') " + 
     "group by status") 

另一種方法是使用存儲過程,但他們似乎很難與JPA實現,我不認爲他們是必要的。

預期結果:

{ 
    "code":"xxx", 
    "title":"This is the title of the first series" 
    "type":"PERIODIC", 
    "period":"WEEKLY", <-- PERIOD 
    "from":"2015-08-02", 
    "to":"2015-08-29", 
    "labels": ["2015-08-02", "2015-08-09", "2015-08-16", "2015-08-23"], 
    "tabelType": "TEXT", 
    "series":[ 
     { 
     "code":"xxx", 
     "title":"This is the title of the first series" 
     "values":[10, 0, 13, 18] <- in this example, we don't have values for label "2015-08-09" 
     }, 
     { 
     "code":"xxx", 
     "title":"This is the title of the second series" 
     "values":[10, 0, 13, 18] <- in this example, we don't have values for label "2015-08-09" 
     } 
    ] 
} 
+1

在Postgres裏,這通常用做['SELECT ... FROM generate_series(timestamp_from,timestamp_to,間隔)AS int_start JOIN your_data ON your_data.timestamp_col BETWEEN int_start AND int_start + interval'](https://www.postgresql.org/docs/current/static/functions-srf.html),但我想不出JPA友好的方式(其他比使用原生查詢)。 – pozs

+0

@pozs,是的,如何在JPA上做到這一點是最困難的部分。我想也許使用映射到JPA實體的視圖,但我不知道是否會工作。 –

回答

2

@pozs在這裏提供了答案。這隻能用本地查詢(PostgreSQL)完成。結果如下:

/** 
* Returns the counts, totals and averages of the states by their currency, period and status. 
*/ 
@Query(value = "select i.currency, date(p), i.status, count(id), sum(coalesce(i.total, 0)), avg(coalesce(i.total, 0)) " + 
    "from generate_series(date_trunc(:period, cast(:from as timestamp)), date_trunc(:period, cast(:to as timestamp)) + cast('1 ' || :period as interval), cast('1 ' || :period as interval)) p " + 
    "inner join invoice i on i.due_date >= p and i.due_date < p + cast('1 ' || :period as interval) " + 
    "where issuer_id = :issuerId and type = :type and (:recipientId = 0 or recipient_id = :recipientId) and type = :type " + 
    "group by i.currency, date(p), i.status " + 
    "order by i.currency, date(p), i.status", nativeQuery = true) 
List<Object[]> getIssuerStatementTotalsByCurrencyPeriodAndStatus(
    @Param("issuerId") long issuerId, 
    @Param("recipientId") long recipientId, 
    @Param("type") String statementType, 
    @Param("from") String from, 
    @Param("to") String to, 
    @Param("period") String period); 

請注意,這將返回對象數組的列表。另外請注意,我無法將枚舉和複雜參數傳入該方法。我不得不將這些值貶低爲字符串和原語。

我已經把這個結果到一些有意義的事情下面的類:

/** 
    * Contains the result of a single result in an aggregate query. 
    */ 
    public class AggregateResult { 

     private List<String> keys; 
     private List<BigDecimal> values; 

     @SuppressWarnings("unused") 
     public AggregateResult(Object value1, Object value2) { 
      this(new Object[] { value1, value2 }); 
     } 

     @SuppressWarnings("unused") 
     public AggregateResult(Object value1, Object value2, Object value3) { 
      this(new Object[] { value1, value2, value3 }); 
     } 

     @SuppressWarnings("unused") 
     public AggregateResult(Object value1, Object value2, Object value3, Object value4) { 
      this(new Object[] { value1, value2, value3, value4 }); 
     } 

     @SuppressWarnings("unused") 
     public AggregateResult(Object value1, Object value2, Object value3, Object value4, Object value5) { 
      this(new Object[] { value1, value2, value3, value4, value5 }); 
     } 

     public AggregateResult(Object... vals) { 
      values = new ArrayList<>(); 
      while (values.size() < vals.length && vals[vals.length - values.size() - 1] instanceof Number) { 
       Number number = (Number) vals[vals.length - values.size() - 1]; 
       values.add(number instanceof BigDecimal ? (BigDecimal) number : new BigDecimal(number.toString())); 
      } 

      this.keys = Stream.of(ArrayUtils.subarray(vals, 0, vals.length - values.size())).map(Object::toString).collect(toList()); 
     } 

     public List<String> getKeys() { 
      return keys; 
     } 

     public List<BigDecimal> getValues() { 
      return values; 
     } 

     /** 
     * Returns the list of {@link AggregateResult}s for the raw result. The raw result is expected to 
     * have been returned from a native JPA query. 
     */ 
     public static List<AggregateResult> fromNativeResult(List<Object[]> raw) { 
      return raw.stream().map(AggregateResult::new).collect(toList()); 
     } 
    } 
1

這可能不是直接回答你的問題,而是:你爲什麼需要JPA查詢,而不是直接在Java代碼中做了分組?這種類型的複雜語義分組是使用Java很好完成的,但是SQL數據庫通常不太擅長生成這種類型的結構化數據。如果在數據庫級別上沒有其他原因這樣做(例如,您需要填充可根據期限搜索的數據的視圖),那麼只需加載原始數據並使用Java代碼填充結構 - 它將少得多編碼開銷也可能更高效。

+0

感謝您的回答,但我認爲在Java代碼中執行此類事情實際上應該會更慢。通常SQL更快。 –

+0

這嚴格取決於您想要執行的操作類型。但是,由於Java在內存中運行並且SQL服務器通常不會(除了緩存的查詢/索引之外),編程良好的Java代碼通常會比其SQL代碼更快。 但是,當涉及到各種類型的即席分組時,在大多數情況下,Java會比SQL更快。此外,您必須記住代碼清晰性和可管理性的隱藏成本。即使數據庫解決方案更快,問題在於您是否想用難以閱讀和維護的解決方案付出代價。 –

相關問題