MySQL DB or JDBC driver not handling LocalDate.MAX - java

I am trying to run a query with a start date and end date as parameters, which may be null.
MySQL - the DB or the JDBC driver or in combination - cannot handle null values by which I mean, the SQL AND endDate <= null excludes every row.
So I am trying to use startDate >= LocalDate.MIN which works well for the "no start date".
But endDate <= LocalDate.MAX doesn't work to allow any date. This is the SQL template:
String strStmt = "SELECT d.forecastId, d.valueDate, d.value " +
"FROM data d " +
"WHERE d.forecastId = ? " +
"AND valueDate >= ? " +
"AND valueDate <= ? " +
and this is the variable allocation:
if (startDate == null) {
statement.setObject(2, LocalDate.MIN);
} else {
statement.setObject(2, startDate.toInstant().atZone(
ZoneId.systemDefault()).toLocalDate());
}
if (endDate == null) {
statement.setObject(3, LocalDate.MAX);
} else {
statement.setObject(3, endDate.toInstant().atZone(
ZoneId.systemDefault()).toLocalDate());
}
try (ResultSet rs = statement.executeQuery()) {
...
and this is the SQL which it produces (via P6Spy):
SELECT valueDate FROM data d WHERE d.forecastId = 52010
AND valueDate >= '-999999999-01-01'
AND valueDate <= '+999999999-12-31'
This doesn't work, I get no rows.
If I then change it to something less extreme:
if (endDate == null) {
statement.setObject(3, LocalDate.parse("2019-01-01"));
...
I see the following in the P6Spy logging:
SELECT valueDate FROM data d WHERE d.forecastId = 52010
AND valueDate >= '-999999999-01-01'
AND valueDate <= '2019-01-01'
and the query works fine, except of course at some point in the future.
Is this some sort of overflow in MySQL? Should I use LocalDate.MAX differently or what can I use instead of LocalDate.MAX?
I don't wish to revert to
statement.setDate(3, new java.sql.Date(Long.MAX_VALUE);
which produces the following:
AND valueDate <= '17-Aug-94'
which doesn't work either - maybe a Y2K or Year 2038 bug? P6Spy is not helping by displaying it with its own unhelpful format.

Exceeding limits of the data type DATE in MySQL
The data type DATE in MySQL 5.7 is limited to a much smaller range of values than the java.time.LocalDate type. Quoting from that doc, (emphasis mine):
The DATE type is used for values with a date part but no time part. MySQL retrieves and displays DATE values in 'YYYY-MM-DD' format. The supported range is '1000-01-01' to '9999-12-31'.
In contrast, java.time.LocalDate.MAX is the date +999999999-12-31. The year 999999999 is well beyond the year 9999.
If your goal is recording a particular date far in the future as a stand-in for an unknown or indeterminate date, choose one not quite so far out. Or use NULL, but neither Dr. Chris Date nor I recommend using NULL.
Date-time capabilities vary widely amongst various databases. The SQL standard barely touches on the subject. So you must carefully read the documentation of your particular database to learn the limits and the behavior of that product. Likewise, your JDBC driver.

Assuming you're not intending to filter out rows that have a null value in valueDate, it seems that you're trying to create a condition that always evaluates to true when either the start or end date in the query is null. I think it would be simpler to just omit the (redundant) comparison in the null end/start date case, by generating the SQL dynamically:
String strStmt = "SELECT d.forecastId, d.valueDate, d.value " +
"FROM data d " +
"WHERE d.forecastId = ? ";
if (startDate != null) {
strStmt += "AND valueDate >= ? ";
}
if (endDate != null) {
strStmt += "AND valueDate <= ?";
}
Then, after creating the statement, you would dynamically add the correct number of date parameters:
int index = 1;
statement.setInt(index++, forecastId);
if (startDate != null) {
statement.setObject(index++, startDate.toInstant().atZone(
ZoneId.systemDefault()).toLocalDate());
}
if (endDate != null) {
statement.setObject(index, endDate.toInstant().atZone(
ZoneId.systemDefault()).toLocalDate());
}
try (ResultSet rs = statement.executeQuery()) {
//...

Related

Check if java Timestamp is null in jpql query

I have the following code:
#Query("select t from Training t join t.skills s join t.trainers tr join t.discipline d where " +
"(t.name in :names or :names is null) and (s.name in :skills or :skills is null) and" +
" (t.location = :location or :location is null) and " +
" (d.name = :discipline or :discipline is null) and " +
"(tr.firstName in :trainers or :trainers is null) and " +
" (((:endDate > t.endDate) and (:startDate < t.startDate)) or (:startDate is empty))")
public List<Training> filterTrainings(List<String> names, List<String> skills, String location,String discipline,List<String> trainers,Timestamp endDate,Timestamp startDate);
and i need to check if :startDate and :endDate are null. Is there a way to do that?
The error i get is nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
when trying to check :startDate is null where start date is a Timestamp.
Could you try passing a LocalDateTime as a parameter instead of a Timestamp? java.sql.Timestamp might be causing you issues here. You can convert to a LocalDateTime by calling timestamp.toLocalDateTime()
Alternatively you could try passing the timestamp as a string into filterTrainings. If the timestamp is null before calling the filterTrainings method, assign an empty string. String _timestamp = timestamp == null ? "" : timestamp.toString()
Then, in your sql statement check if the string is empty .. ""=:timestamp OR function("to_timestamp", :timestamp, "yyyy-mm-dd hh:mm:ss.fffffffff"). The problem here is that we are using function to access native db commands.
Maybe you can build your SQL query with a and COALESCE(field, default_value_for_datetime_field). So you will not get NULL values.
You can build a default value as the start of the current month, of the current week, or any datetime value which could be a fair substitution for your model domain.

how to load data from mysql using in between two dates

In the code below using java, I am trying to get data from mysql between two choosed dates and load it into table jTable1 but the problem is when I run the data loads correctly but not until the second date.
For example, if first date is 2020-1-1 and second date is 2020-1-31 the data loads from the day of 1 to the day of 30 not until the day of 31. The last day is missing! And if I choose dates from 2020-01-01 to 2020-02-01 it loads from 2020-01-01 to 2020-01-31.
java.sql.Timestamp timestamp = new java.sql.Timestamp(jDateChooser5.getDate().getTime());
java.sql.Timestamp timestamp2 = new java.sql.Timestamp(jDateChooser6.getDate().getTime());
String sql2 = "SELECT sum(`msareffishmarket`.`Amount` ) FROM `manfz`.`msareffishmarket` \n"
+ "Where `msareffishmarket`.`place` = 'محل الأسماك' And ( `msareffishmarket`.`MsarefDate` between '" + timestamp + "' And '" + timestamp2 + "' ) "
+ " group by DATE(`msareffishmarket`.`MsarefDate`)";
stm2 = conn.prepareStatement(sql2);
rs2 = stm2.executeQuery(sql2);
// JOptionPane.showMessageDialog(this, rs.getDouble(4));
jTable3.setModel(DbUtils.resultSetToTableModel(rs2));
You are using your prepared statement incorrectly (though you are correct to think to use one). Consider this version of your code:
String sql = "SELECT MsarefDate, SUM(Amount) AS total FROM manfz.msareffishmarket ";
sql += "WHERE place = 'محل الأسماك' AND MsarefDate BETWEEN ? AND ? GROUP BY MsarefDate";
stm2 = conn.prepareStatement(sql);
stm2.setTimestamp(1, timestamp);
stm2.setTimestamp(2, timestamp2);
rs2 = stm2.executeQuery(); // do not pass the query string here
Edit:
Per the correct observations of #mangusta (see below), you should be using a date/timestamp range to search for your records. Assuming you wanted all records from January 2020, you should be using:
WHERE MsarefDate >= '2020-01-01' AND MsarefDate < '2020-02-01'

SYSDATE in JDBC statements seems to return different times when queried against the same Oracle database

We have 3 to 4 environments with identical setup, each with multiple clustered application servers (WebSphere) and Oracle Supercluster databases.
The problem I am seeing seems to be happening in only one of the environments that too about 20% of the time.
We have
Two applications deployed to same application server cluster
Both applications use the same data source configured to use the Oracle database which is a Oracle Supercluster.
Following is the structure of the table in question
TABLE SESSION_TBL
(
ID NUMBER(12, 0) NOT NULL,
USER_ID VARCHAR2(256 BYTE) NOT NULL,
SESSION_ID VARCHAR2(60 BYTE) NOT NULL,
LOGIN_TIME TIMESTAMP(6) NOT NULL
)
Application 1 stores a record using JDBC
String sql = "Insert into SESSION_TBL " +
" (USER_ID, SESSION_ID, LOGIN_TIME ) " +
" values (?,?,SYSDATE)";
try
{
sessionId = getNewSessionId(userId);
st = conn.prepareStatement(sql);
st.setString(1, userId);
st.setString(2, sessionId);
int rows = st.executeUpdate();
}
After a few seconds, Application 2 executes the following code to look up the record inserted by the first application and compares with the current time
Statement st = null;
ResultSet result = null;
String sql = " Select * " +
" from SESSION_TBL " +
" where USER_ID = '" + userId + "' " +
" and SESSION_ID = '" + sessionId + "' ";
try {
st = conn.createStatement();
result = st.executeQuery(sql);
if(!result.next()) { // We have no data, need to expire the session
logger.debug("End hasSessionExpired()");
return true;
}
else {
logger.debug("SessionInfo:ResultSet not null");
}
// Get the time user logged in
// java.sql.Timestamp
Timestamp database_date = result.getTimestamp("LOGIN_TIME"); // get date from db
long databaseDate = database_date.getTime(); // Convert to UNIX time
// Get the current time from the database, so we are getting the time
// from the same sources to compare
// See the code below for this function
long currentTime = getCurrentTimeFromDB (conn);
// Both time values would be in milli seconds
long diffSecs = (currentTime - databaseDate)/1000;
logger.info ("db:" + databaseDate + " now:" + currentTime + " diffSecs:" + diffSecs);
Code to get the current time from the database
public static long getCurrentTimeFromDB () {
.
.
// Using SYSDATE. We only need precision upto seconds
String s = "SELECT SYSDATE NOW FROM DUAL";
rs = statement.executeQuery(s);
if (rs.next()) {
Timestamp dbTime = rs.getTimestamp("NOW");
currentTime = dbTime.getTime();
}
return currentTime;
}
In about 1 of 5 or so executions, I see the current time to be earlier than the time of the record creation (login time). When this happens I see an output of the debug statement like the following:
db:1538793249000 now:1538793023000 diffSecs:-226
db:1538793249000 now:1538793023000 diffSecs:-202
db:1538793249000 now:1538793023000 diffSecs:-225
Seems like about 200+ seconds earlier
If you notice one thing the data type of the column (LOGIN_TIME) is a Timestamp and I am using SYSDATE to populate it. However, I am also using SYSDATE to get the time from DUAL. Of the 4 environments we have, this is happening in one and not always. Is there something wrong in the code or is it possible that the database Oracle super cluster) is actually returning a date that is not correct.
I thought I would put an answer in case someone else runs into a similar problem. As folks have suggested (in the comments) that the cluster nodes times could be out of sync. We have 2 nodes in the cluster, one node was ahead (of the current time) by a couple of minutes. One could detect if the time is out of sync, by running a SYSDATE query against each node.

JPA 'where in' to be considered only if List passed as parameter has elements

I have one query that should filter based on various parameters; one of those parameters is a list. If there are entries in the list, there should be a filtering based on the entries; but if the list is empty/null, there shouldn't be any filtering on that field.
What I've thought is something like this:
#Query("select a from Alert a where a.date >= :startDate " +
"and (((:countryIds) is null) or a.countryId in (:countryIds)) " +
"and (((:typeIds) is null) or a.siteTypeId in (:typeIds)) ")
List<Alert> findBy(#Param("startDate") Date startDate,
#Param("countryIds") Set<Long> countryIds,
#Param("typeIds") Set<Long> typeIds);
Sending null List it throws NPE; sending an empty list it generates the following SQL, which is invalid
where alert0_.date >= '2018-01-01' and
((1, 123) is null or alert0_.countryId in (1, 123))
I've also tried in JPQL to have and (((:countryIds) is empty) or a.countryId in (:countryIds)) but it also doesn't work when trying to compile the JPQL (at application startup): Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: ??? is not mapped
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:171)
Or using SpEL:
"and (:#{countryIds.size() > 0} or (a.countryId in (:countryIds))) "
but again, it doesn't compile the JPQL.
The only solution I've thought is to dynamically generate the JPQL which is ugly or to populate all existing values for countryIds and siteTypeIds which is inefficient.
JPA implementation is Hibernate and database is MySQL.
After lots of trial and error I found an acceptable working solution with SpEL; thought some might find it useful:
#Query("select a from Alert a where a.date >= :startDate "
"and (:#{#countryIds == null} = true or (a.countryId in (:countryIds))) " +
"and (:#{#siteTypeIds == null} = true or (a.siteTypeId in (:siteTypeIds))) ")
List<Alert> findBy(#Param("startDate") Date startDate,
#Param("countryIds") Set<Long> countryIds,
#Param("siteTypeIds") Set<Long> siteTypeIds);
The Sets sent as parameters have to be null instead of empty sets.
It yields an acceptable SQL:
select alert0_.alertId as alertId1_0_, [...]
from alert alert0_
where alert0_.date >= '2018-01-01' and
(0 = 1 or alert0_.countryId in (1, 123)) and
(1 = 1 or alert0_.siteTypeId in (null));
I had the same problem so im writing extended solution with using also embedded parameter
#Query("from PartPrice where "
+ "customer in :#{#customers} and "
+ "( (:#{#suppliers == null || #suppliers.size() == 0} = true and supplier is null) or (:#{#suppliers != null && #suppliers.size() > 0} = true and supplier in :#{#supplier}) ) and "
+ " productIdentifier.manufacturerId = :#{#productIdentifier.manufacturerId} and productIdentifier.productNumber = :#{#productIdentifier.productNumber} and "
+ " ( (:#{#isAbsPrice} = true and abs_price is not null) or (:#{#isAbsPrice} = false and abs_price is null) ) "
+ " and (validUntil is null or validUntil >= :#{#fromDate}) and (:#{#untilDate == null} = true or validFrom <= :#{#untilDate}) ")
where suppliers is nullable, empty or contains values and productIdentifier is embedded id containing productNumber and manufacturerId passing as
#Param("productIdentifier") ProductIdentifier productIdentifier
Also interval is valid from fromDate to null (forever) or untilDate.
As for me the best solution for such cases is Criteria API, if you not familiar with it you can find some information here:
https://www.objectdb.com/java/jpa/query/criteria

Why is the date/timestamp difference always NULL?

I just coded this way:
Cursor cursor;
String sql = "SELECT l.acao, SUM(strftime('%s',l.data_fim) - strftime('%s',l.data_inicio)) AS total_time," +
"FROM logs AS l " +
"WHERE l.data_fim IS NOT NULL " +
"GROUP BY l.acao";
I need to sum the seconds between two dates and I named this sum total_time. But when I try to get the result of total_time it always returns null, see next code:
String totalTimeInSeconds = (String) cursor.getString(cursor.getColumnIndex("total_time"));
When I put this same query in SqlLiteStudio it works perfectly. What am I doing wrong?
Your problem is that the value in the cursor actually is NULL.
This can happen only when the original date values are not in one of the supported date formats. You have to change your database.
instead of using cursor.getColumnIndex(), you can enter the exact index in it. In your case the total_time index is 1. Ex : cursor.getString(1) or cursor.getInt(1)

Categories

Resources