Keith McMillan
Keith McMillan

Reputation: 21

Single-quoting parameter values in Spring JPA/Hibernate Native query (PostgreSQL)

I have a database that stores an event in an "event" table, with multiple readings for that event stored in a "reading" table, with a foreign key of the event id. The event itself has a timestamp stored in UTC (Timestamp with Time Zone). I have users however in multiple time zones, and want to summarize the readings on a given day, in the user's timezone.

I'm able to successfully query to retrieve the maximum, minimum and average reading for a given day (utc) using a native query:

@Query(nativeQuery=true, value="SELECT source, to_char(timestamp, 'yyyy-MM-dd') as \"date\", min(decimalreading), max(decimalreading), avg(decimalreading) " +
                "FROM reading, event " +
                "WHERE reading.event = event.id and source = ?1 "+
                "and timestamp between ?2 and ?3 "+
                "GROUP BY event.source, to_char(timestamp, 'yyyy-MM-dd') " +
                "ORDER BY source, to_char(timestamp, 'yyyy-MM-dd')")
List<Object[]> minMax(long source, Calendar from, Calendar to);

Postgres nicely supports the "timestamp at time zone" syntax, so I tried:

@Query(nativeQuery=true, value="SELECT source, to_char(timestamp at time zone ?4, 'yyyy-MM-dd') as \"date\", min(decimalreading), max(decimalreading), avg(decimalreading) " +
                "FROM reading, event " +
                "WHERE reading.event = event.id and source = ?1 " +
                "and timestamp at time zone ?4 between ?2 and ?3 "+
                "GROUP BY event.source, to_char(timestamp at time zone ?4, 'yyyy-MM-dd') " +
                "ORDER BY source, to_char(timestamp at time zone ?4, 'yyyy-MM-dd')")
List<Object[]> minMax(long source, Calendar from, Calendar to, String timezone);

With the query in this form, I get

event.timestamp must appear in GROUP_BY clause or be used in an aggregate function

Debugging the Hibernate SQL shows the query as:

DEBUG SQL:109 - SELECT source, to_char(timestamp at time zone ?, 'yyyy-MM-dd') as "date", min(decimalreading), max(decimalreading), avg(decimalreading) FROM reading, event WHERE reading.event = event.id and timestamp at time zone ? between ? and ? GROUP BY event.source, to_char(timestamp at time zone ?, 'yyyy-MM-dd') ORDER BY source, to_char(timestamp at time zone ?, 'yyyy-MM-dd')

If I enclose ?4 in single quotes ('?4') I get

java.lang.IllegalArgumentException: Parameter with that position [4] did not exist

so clearly that works no better, and it doesn't see the parameter in the query at all.

I've also tried ''?4'' and \'?4\', with no better success.

If I replace the ?4 positional parameter with a hard-coded value of 'cst5cdt', the query executes successfully. It also executes nicely via a sql prompt in pgadmin.

I suspect this is a problem with either the prepared statement, or with the way the positional parameters are being handled.

Is there a syntax that will allow me to aggregate the data for the day, in the time zone passed in as a parameter?

Upvotes: 2

Views: 2735

Answers (1)

Keith McMillan
Keith McMillan

Reputation: 21

I found one way to do this, although it's a bit complicated. Selecting the timezone as a subquery allows it to be interpolated correctly into the main query:

@Query(nativeQuery=true, value="SELECT source, to_char(timestamp at time zone local.tz, 'yyyy-MM-dd') as \"date\", min(decimalreading), max(decimalreading), avg(decimalreading) " +
                "FROM reading, event, (select ?4 as tz) as local " +
                "WHERE reading.event = event.id "+
                "and source = ?1 " +
                "and timestamp at time zone local.tz between ?2 and ?3 "+
                "GROUP BY event.source, to_char(timestamp at time zone local.tz, 'yyyy-MM-dd') " +
                "ORDER BY well, to_char(timestamp at time zone local.tz, 'yyyy-MM-dd')")
List<Object[]> minMax(long source, Calendar from, Calendar to, String timezone);

Interested to hear if there are other ways that might be less... crufty.

Upvotes: 0

Related Questions