How to cast a null date in an hibernate nativeQuery? - postgresql

I'm using postgreSQL and hibernate. Because I've to use SQL queries that is not take into account by hibernate, I use nativeQuery in my Query annotation.
I have multiple dates param and inevitably at least two of them are null.
I have to check a condition only if my param isn't null. The problem is that I'm not able to cast my date if this one is null.
I try different possible solutions but nothing works
:dEchTransacEqual is null OR transaction.d_ech_transac = cast(:dEchTransacEqual as date)
coalesce(:dEchTransacGreaterThanEqual, null) is null OR transaction.d_ech_transac >= cast(:dEchTransacGreaterThanEqual as date)
CASE cast(:dEchTransacGreaterThanEqual as date) WHEN not null THEN transaction.d_ech_transac >= cast(:dEchTransacGreaterThanEqual as date) END
CASE coalesce(cast(:dEchTransacGreaterThanEqual as boolean), null) WHEN not null THEN transaction.d_ech_transac >= cast(:dEchTransacGreaterThanEqual as date) END
to_date(cast(:dEchTransacGreaterThanEqual as date), 'YYYY/MM/DD') is null OR transaction.d_ech_transac >= cast(:dEchTransacGreaterThanEqual as date)
:dEchTransacGreaterThanEqual is null OR transaction.d_ech_transac >= to_date(cast(:dEchTransacGreaterThanEqual as date), 'YYYY/MM/DD')
I don't have more idea to solve my problem.
How can I handle the nullity of my date using the nativeQuery of hibernate?
The last (and dirty) solution is to implement as many repo method as I have use cases (3).
PS. : I have to use nativeQuery because I'm using over() and partition by that isn't handle by Hibernate.

It looks like PG does not support "IS NULL" test for parameters. It means that you must build a query dynamically. If you don't want manipulate strings, you may use FluentJPA's Dynamic Queries, which also supports OVER clause.

Related

Convert ABAP date to HANA date returning NULL if empty

My task is to convert a ABAP style date (i.e. 2017-11-20 which is represented as string "20171120") to a HANA date via sql script. This can easily be done by:
select to_date('20171120','YYYYMMDD') from dummy;
But there is another requirement: if the abap date is initial (value '00000000') the database shall store a null value. I have found a working solution: I replace the potential initial date '00000000' with 'Z' and trim the string to null if only 'Z' is found:
select to_date(trim(leading 'Z' from replace('00000000','00000000','Z')),'YYYYMMDD') from dummy;
-- result: null
select to_date(trim(leading 'Z' from replace('20171120','00000000','Z')),'YYYYMMDD') from dummy;
-- result: 2017-11-20
But this looks like a dirty hack. Has anybody an idea for a more elegant solution?
As explained in my presentation Innovation with SAP HANA - What are my options all that string manipulation is really not necessary.
Instead, use the appropriate conversion functions when dealing with ABAP date and time data. In this case, DATS_TO_DATE is the correct function.
with in_dates as
( select '20171120' as in_date from dummy
union all select '00000000' as in_date from dummy)
select
dats_to_date(in_date)
, in_date
from in_dates;
|DATS_TO_DATE(IN_DATE) |IN_DATE
-------------------------+---------
|2017-11-20 |20171120
|? |00000000
The ? here is the output representation for NULL.
DATS_TO_DATE does not return NULL if the given date is initial (0000-00-00), but a special date value (-1-12-31 to be precise).
To receive a NULL value in this case, as you requested, use the following statement:
NULLIF( DATS_TO_DATE(?), DATS_TO_DATE('00000000'))
e. g.:
INSERT INTO null_test VALUES (NULLIF( DATS_TO_DATE('00000000'), DATS_TO_DATE('00000000')));
=> returns NULL
INSERT INTO null_test VALUES (NULLIF( DATS_TO_DATE('20171224'), DATS_TO_DATE('00000000')));
=> returns 2017-12-24
As there are no tedious string operations involved, this statement should yield good performance.

Passing a null parameter to a native query using #Query JPA annotation

In a Spring Boot application, I have a SQL query that is executed on a postgresql server as follows :
#Query(value = "select count(*) from servers where brand= coalesce(?1, brand) " +
"and flavour= coalesce(?2, flavour) ; ",
nativeQuery = true)
Integer icecreamStockCount(String country, String category);
However,
I get the following error when I execute the method :
ERROR: COALESCE types bytea and character varying in PostgreSQL
How do I pass String value = null to the query?
**NOTE : ** I found that my question varied from JPA Query to handle NULL parameter value
You need not coalesce, try this
#Query("select count(*) from servers where (brand = ?1 or ?1 is null)" +
" and (flavour = ?2 or ?2 is null)")
Integer icecreamStockCount(String country, String category);
When I encounted this error, I ended up using a combination of OR and CAST to solve the issue.
SELECT COUNT(*)
FROM servers
WHERE (?1 IS NULL OR brand = CAST(?1 AS CHARACTER VARYING))
AND (?2 IS NULL OR flavour = CAST(?2 AS CHARACTER VARYING))
This works even if ?1, ?2, brand and flavor are all nullable fields.
Note that passing null for ?1 means "all servers regardless of brand" rather than "all servers without a brand". For the latter, you could use IS DISTINCT FROM as follows.
SELECT COUNT(*)
FROM servers
WHERE (CAST(?1 AS CHARACTER VARYING) IS NOT DISTINCT FROM brand)
AND (CAST(?2 AS CHARACTER VARYING) IS NOT DISTINCT FROM flavour)
Finally, certain parameter types such as Boolean cannot be cast in SQL from BYTEA to BOOLEAN, for those cases you need a double cast:
SELECT COUNT(*)
FROM servers
WHERE (?1 IS NULL OR is_black = CAST(CAST(?1 AS CHARACTER VARYING) AS BOOLEAN))
In my eyes this is a problem in Hibernate which could be solved by passing Java null parameters as plain SQL NULLs rather than interpreting null as a value of type BYTEA.
If you really need to use native query, there is a problem because it's an improvement not implemented yet, see hibernate. If you don't need to use native you can do (where ?1 is null or field like ?1). Assuming you do need native,
you may treat the String before by setting this empty and then calling the repository and this one would be like:
#Query(value = "select count(*) from servers where (?1 like '' or brand like ?1) " +
"and (?2 like '' or flavour like ?2)",
nativeQuery = true)
Integer icecreamStockCount(String country, String category);
There is always javax.persistence.EntityManager bean as option for native query situations and I recommend it instead of previous approach. Here you can append to your query the way you want, as follows:
String queryString = "select count(*) from servers ";
if (!isNull(country)) queryString += "where brand like :country";
Query query = entityManager.createNativeQuery(queryString);
if (!isNull(country)) query.setParameter("country", country);
return query.getResultList();
Observations:
Newer versions have improved this '+' concatenation Strings. But you can build your queryString the way you want with StringBuilder or String Format, it doesn't matter.
Be careful with SQL injection, the setParameter method avoid this kind of problem, for more information see this Sql Injection Baeldung
So this is not the exact answer to the question above, but I was facing a similar issue, I figured I would add it here, for those that come across this question.
I was using a native query, in my case, it was not a singular value like above, but I was passing in a list to match this part of the query:
WHERE (cm.first_name in (:firstNames) OR :firstNames is NULL)
I was getting the bytea error, in the end I was able to send an empty list.
(null == entity.getFirstName()? Collections.emptyList() : entity.getFirstName())
In this case, sending the empty list to the resolver worked, where as null did not.
hope this saves you some time.
null parameters are not allowed before Hibernate 5.0.2.
See https://hibernate.atlassian.net/browse/HHH-9165
and the replies to https://www.postgresql.org/message-id/6ekbd7dm4d6su5b9i4hsf92ibv4j76n51f#4ax.com

tsql convert string into date when possible

I've got a column to import into an Azure SQL DB that is supposed to be made of dates only but of course contains errors.
In TSQL I would like to do something like: convert to date if it's possible otherwise null.
Does anyone know a statement to test the convertibility of a string into a date?
use TryCast or Isdate
select
try_Cast('test' as date)
select try_Cast('4' as date)
select case when ISDATE('test')=1 then cast('test' as date) else null end
TryCast will fail if the expression is not in expected format ..ie.,if the explicit conversion of expression is not permitted
select
try_cast( 4 as xml)
select try_Cast(4 as date)
You could use TRY_PARSE:
Returns the result of an expression, translated to the requested data type, or null if the cast fails. Use TRY_PARSE only for converting from string to date/time and number types.
SELECT TRY_PARSE('20129901' AS DATE)
-- NULL
Additionaly you could add culture:
SELECT TRY_PARSE('10/25/2015' AS DATE USING 'en-US')
And importing:
INSERT INTO target_table(date_column, ...)
SELECT TRY_PARSE(date_string_column AS DATE) ...
FROM source_table
...

How to convert a '' to NULL in postgreSQL

I have a postgreSQL table which accepts date in yyyy-mm-dd format and it is not accepting if the incoming date format is ''(no date). There could be some instances when '' gets passed as date. Could anyone help me write a function which checks if the incoming date is '' and then replaces it with NULL and then adds it to the db.
Use nullif()
insert into the_table (the_date_column)
values (nullif(?, ''))
Or for an update
update the_table
set the_date_column = nullif(?, '');
You could use a case expression to check for this. I'm using :arg to represent the inputting string - change it according to the programming language you're using:
INSERT INTO mytable
(my_date_col)
VALUES (CASE LENGTH(:arg) WHEN 0 THEN NULL ELSE TO_DATE(:arg, 'yyyy-mm-dd' END)

SUBSTR does not work with datatype "timestamp" in Postgres 8.3

I have a problem with the query below in postgres
SELECT u.username,l.description,l.ip,SUBSTRING(l.createdate,0,11) as createdate,l.action
FROM n_logs AS l LEFT JOIN n_users AS u ON u.id = l.userid
WHERE SUBSTRING(l.createdate,0,11) >= '2009-06-07'
AND SUBSTRING(l.createdate,0,11) <= '2009-07-07';
I always used the above query in an older version of postgres and it worked 100%. Now with the new version of posgres it gives me errors like below
**ERROR: function pg_catalog.substring(timestamp without time zone, integer, integer) does not exist
LINE 1: SELECT u.username,l.description,l.ip,SUBSTRING(l.createdate,...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.**
I assume it has something to do with datatypes, that the data is a time zone and that substring only support string datatypes, now my question is what can I do about my query so that my results would come up?
The explicit solution to your problem is to cast the datetime to string.
...,SUBSTRING(l.createdate::varchar,...
Now, this isn't at all a good practice to use the result to compare dates.
So, the good solution to your need is to change your query using the explicit datetime manipulation, comparison and formatting functions, like extract() and to_char()
You'd have to change your query to have a clause like
l.createdate::DATE >= '2009-06-07'::DATE
AND l.createdate::DATE < '2009-07-08'::DATE;
or one of the alternatives below (which you should really accept instead of this.)
SELECT u.username, l.description, l.ip,
CAST(l.createdate AS DATE) as createdate,
l.action
FROM n_logs AS l
LEFT JOIN
n_users AS u
ON u.id = l.userid
WHERE l.createdate >= '2009-06-07'::TIMESTAMP
AND l.createdate < '2009-07-07'::TIMESTAMP + '1 DAY'::INTERVAL
I'm not sure what you want to achieve, but basically "substring" on date datatypes is not really well defined, as it depends on external format of said data.
In most of the cases you should use extract() or to_char() functions.
Generally - for returning data you want to_char(), and for operations on it (including comparison) - extract(). There are some cases where this general rule does not apply, but these are usually signs of not really well thought data-structure.
Example:
# select to_char( now(), 'YYYY-MM-DD');
to_char
------------
2009-07-07
(1 row)
For extract let's write a simple query that will list all objects created after 8pm:
select * from objects where extract(hour from created) >= 20;
A variation on the Quassnoi's answer:
SELECT
u.username,
l.description,
l.ip,
CAST(l.createdate AS DATE) as createdate,
l.action
FROM
n_logs AS l
LEFT JOIN
n_users AS u
ON
(u.id = l.userid)
WHERE
l.createdate::DATE BETWEEN '2009-06-07'::DATE AND '2009-07-07'::DATE
If you use Postgresql, you will receive:
select('SUBSTRING(offer.date_closed, 0, 11)')
function substr(timestamp without time zone integer integer) does not
exist
Use:
select('SUBSTRING(CONCAT(offer.date_closed, \'\'), 0, 11)')