2016-08-09 122 views
0

Ich muss das erwartete Ergebnis unten generieren. Im Grunde handelt es sich um eine Abfrage, um Werte nach einem bestimmten Zeitraum (WOCHE, MONATLICH usw.) zu aggregieren. Es gibt einen Datumsfilter mit Anfang und Ende, und wir müssen Werte für den gesamten Bereich zurückgeben. Wenn sie nicht vorhanden sind, sollte 0 zurückgegeben werden.Was ist der richtige Ansatz zur Behandlung von JPA-Periodenabfragen?

Im folgenden Beispiel ist das Startdatum '2015-08-02' und das Datum '2015-08-23' und der Zeitraum ist WOCHE. Beachten Sie, dass wir für Woche 2 keine Werte haben, aber es sollte mit einem Nullwert zurückgegeben werden.

Also, in diesem Fall, was ist der beste Ansatz, dies mit JPA zu tun? Wir denken über die Verwendung von temporären Tabellen nach und verknüpfen die Ergebnisse mit dieser Tabelle, um Ergebnisse für den gesamten Bereich zu erhalten, aber ich weiß nicht, ob dies mit JPA möglich ist, da wir die Tabelle erstellen, verknüpfen und dann die temporäre Tabelle löschen müssen.

Eine weitere Option besteht darin, eine Datenbankansicht zu erstellen und sie einer Entität zuzuordnen.

In den vorgenannten Fällen sollte die JPQL Abfrage so ähnlich sein:

@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") 

Eine weitere Option, um eine gespeicherte Prozedur zu verwenden ist, aber sie scheinen schwierig zu sein, mit JPA zu implementieren und ich glaube nicht, dass sie notwendig sind.

Erwartetes Ergebnis:

{ 
    "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

In Postgres wird diese mit in der Regel getan [SELECT ... FROM generate_series (timestamp_from, timestamp_to, Intervall) 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), aber ich kann mir keine JPA freundliche Art vorstellen (andere) als native Abfragen verwenden). – pozs

+0

@ Pozs, ja, wie dies auf JPA zu tun ist der schwierigste Teil. Ich dachte daran, vielleicht eine Ansicht zu verwenden, die einer JPA-Entität zugeordnet ist, aber ich weiß nicht, ob das funktionieren wird. –

Antwort

2

@ Pozs lieferte die Antwort hier. Dies kann nur mit einer nativen Abfrage (PostgreSQL) erfolgen. Hier ist das Ergebnis:

/** 
* 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); 

Beachten Sie, dass dies eine Liste von Object-Arrays zurückgibt. Beachten Sie auch, dass ich keine Enums und komplexe Parameter in die Methode eingeben konnte. Ich musste diese Werte auf Strings und Primitive reduzieren.

Ich habe dieses Ergebnis in etwas Sinnvolles mit der folgenden Klasse gedreht:

/** 
    * 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

Dies könnte nicht die direkte Antwort auf Ihre Frage, aber: Warum brauchen Sie die Gruppierung innerhalb der PPV-Abfrage zu tun direkt anstelle des Java-Code? Diese Art der komplexen semantischen Gruppierung ist etwas, das perfekt mit Java gemacht wird, aber SQL-Datenbanken sind im Allgemeinen nicht sehr gut in der Lage, diese Art von strukturierten Daten zu erzeugen. Wenn es keine anderen Gründe dafür gibt, dies auf Datenbankebene zu tun (z. B. müssen Sie eine Ansicht mit diesen Daten füllen, die nach Periode durchsucht werden kann), laden Sie einfach die Rohdaten und füllen Sie die Struktur mit Java-Code - es wird viel weniger haben Programmieraufwand und wird wahrscheinlich auch effizienter sein.

+0

danke für deine Antwort, aber ich denke, dass diese Art von Sachen in Java-Code eigentlich langsamer sein sollte. Normalerweise ist SQL viel schneller. –

+0

Das hängt ausschließlich von der Art der Operation ab, die Sie ausführen möchten. Da jedoch Java im Speicher arbeitet und SQL-Server dies normalerweise nicht tun (abgesehen von zwischengespeicherten Abfragen/Indizes), ist gut programmierter Java-Code im Allgemeinen schneller als seine SQL-Äquivalente. Wenn es jedoch um verschiedene Arten von Ad-hoc-Gruppierungen geht, ist Java in den meisten Fällen schneller als SQL. Außerdem müssen Sie sich an die versteckten Kosten der Code-Klarheit und Verwaltbarkeit erinnern. Auch wenn die Datenbanklösung schneller ist, stellt sich die Frage, ob Sie den Preis in schwer zu lesenden und zu wartenden Lösungen bezahlen möchten. –