like and nvl in jpql query - springboot JPA - postgresql

i am trying to write the below query into spring boot jpql (postgresql db)
first_name
FROM
employees
WHERE
first_name LIKE concat(concat('%',nvl(:key,first_name) ),'%'); --This is oracle style query
Iam trying to write the same logic in JPQL(am new to JPA and JPL , but i know this simple logic ,we can achieve using the implementation less queires , but the above mentioned is a part of a bigger query).
I tried like below. But stuck actually.
#Query("select pe.packageName from PackageEntity pe where " +
"lower(pe.packageName) like " +
"case when :packageName is null then lower(pe.packageName) else concat('%',lower(:packageName),'%') end " )
List<String> getValues( #Param("packageName") String roomIDList);
Error Message is below
org.postgresql.util.PSQLException: ERROR: function lower(bytea) does not exist Hint: No function matches the given name and argument types. You might need to add explicit type casts. Position: 190
Honestly , i've tried every possible solution , i can think of. found same error in stackoverflow , but there solution doesn't match, I really want to send the null values in the query , if the input is null then need to ignore the validation, for example if the input parameter is null, then i want to completely ignore the validation.

solution :-
#Query("select pe.packageName from PackageEntity pe where " +
":packageName is null or lower(pe.packageName) like
concat('%',lower(:packageName),'%')" )
The above workaround worked actually.
But I implemented like, In cotroller code
if (search.getPackageName() == null) {
LOGGGER.info("Input Name is NULL");
search.setPackageName("==!##$%^&*()!##$%^&*()==");
LOGGGER.info("Input Name is set to Default " + search.getPackageName());
}
searchValue = repoPackage.getPackageAll(search.getPackageName());
in repo, where condition is like below - Not sure , if the solution is the best, but it worked. Like this I avoided sending any null value to jpa.
where
(('==!##$%^&*()!##$%^&*()==' = :packageName)
or lower(pd.package_name) like concat('%', lower(:packageName), '%'))

Related

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

use NamedQuery or Criteria API

I have a complex query e.g. search employees with name, age, address, etc
I would like to append a WHERE clause if a parameter IS NOT NULL.
for example (pseudo code):
if (age != null) // then append age to the where clause
whereClause += whereClause + AND age = :age"
I would like to use a NamedQuery in an XML file, but a NamedQuery does not support conditional where like myBatis
Can anyone help me resolve this problem?
For this simple case/condition you can solve the problem with a NamedQuery:
SELECT e FROM Entity e WHERE (:age IS NULL) OR (:age IS NOT NULL and e.age=:age)
If you want more complex queries (e.g. where you sort dynamically by the column), I would use Criteria API.

Firebird 2.5.x. Extract column names and column datatypes of a result from stored procedure

I have a Firebird 2.5 database .As an example I have stored procedure with a name QRESULT which expected return is:
Parameter - DATATYPE
a - date
b - numeric(18,0)
c - integer
d - varchar(50)
and so on....
I use PHP - PDO to query the firebird database using the procedure QRESULT like this:
SELECT a,b,d from QRESULT() where a = "some value"
I need to run some query before QRESULT procedure and i need it to return the datatype of all the columns that QRESULT would return if it was ran. So i can help user to type proper value for my "where" clause.I know i can set that manually in the user interface, but in the real project there are lots of procedures and if there is a way i can make my filter interface generate dynamically i would be happy about that.If this is not possible for a stored procedure i can make it with select statements.I just need some lead.
The information you want is in the RDB$PROCEDURE_PARAMETERS table, basically what you need is query
SELECT r.RDB$PARAMETER_NAME ParName, F.RDB$FIELD_TYPE ParType
FROM RDB$PROCEDURE_PARAMETERS r
JOIN RDB$FIELDS F ON(F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE)
WHERE r.RDB$PROCEDURE_NAME = 'QRESULT'
AND r.RDB$PARAMETER_TYPE = 1
ORDER BY r.RDB$PARAMETER_TYPE, r.RDB$PARAMETER_NUMBER
Note that the SP name should be in upper case as this is how it is stored into system tables (unless you use quoted identifiers). If you want to get both input and output parameters the delete the r.RDB$PARAMETER_TYPE = 1 predicate from the WHERE (type 0 is input parameters and 1 is output).
The type returned by this query is integer id for the type, quick googling found this:
14,"TEXT "
7,"SHORT "
8,"LONG "
9,"QUAD "
10,"FLOAT "
27,"DOUBLE "
35,"TIMESTAMP "
37,"VARYING "
261,"BLOB "
40,"CSTRING "
45,"BLOB_ID "
12,"DATE "
13,"TIME "
16,"INT64 "
but if you want to have more precise type then see this SO post.

Entity Framework search expression

I have to make search through field EmployeeId.
I also have parameter empId which can be something like this: '123'
In my database there's next values:
'0123' - what I need
'10123'
'1234'
and so on.
My target - take a record which ends on empId and has no or one and more leading 0.
I tried to write something like this:
var result = from member in MembersSet
where SqlMethods.Like(member.EmployeeId, "%" + empId) &&
!(SqlMethods.Like(member.EmployeeId, "%[^0]%" + empId))
select member.Id;
But I get an error
LINQ to Entities does not recognize the method 'Boolean Like(System.String, System.String)' method, and this method cannot be translated into a store expression.
May be there is a workaround?
The SqlMethods is for Linq-To-Sql, not EntityFramework. There's an equivalent for EF - SqlFunctions (System.Data.Objects.SqlClient.SqlFunctions). You can use the PatIndex function to do what you want. It's worth noting that this will make your code SQL Server specific.
You can do the initial expression without using PatIndex by using the standard String EndsWith function, which will get translated by EF automatically.
var result = from member in MembersSet
where member.Id.EndsWith("123")
&& SqlFunctions.PatIndex("[^0]%123", member.Id) == 0
select member.Id;
If the values of Id will always be numeric (even with leading zeros), you could try converting them to Integers on the server, by defining an additional method to handle parsing to int (EF won't translate Int32.Parse) - see this question for details.

JPA Native Query

I am trying to execute a native query and pass run-time parameters and get the result as a List. When I try to process the Object [], one of the columns fetched is a String. But it comes out as java.lang.Character instead of String. Here is the query below:
SELECT CASE
WHEN (TRUNC(abm.credit_card_expiration_date) BETWEEN trunc(SYSDATE) AND
trunc(last_day(SYSDATE))) THEN
'Expires'
ELSE
'Expired'
END EXP_STATUS,
TO_CHAR(abm.credit_card_expiration_date, 'MM/YY') EXP_DATE
FROM account_billing_methods abm
WHERE abm.account_id = 201103
AND abm.billing_type_id = 1
AND TRUNC(abm.credit_card_expiration_date) <= TRUNC(LAST_DAY(SYSDATE))
The EXP_STATUS column could not be typecasted into String as it is of type Character. Any ideas of why it does not work?
Regards,
-Anand
I had the same problem and changed the select clause of my query to:
EXP_STATUS || '' as EXP_STATUS
Then it is a VARCHAR instead of a CHAR and JPA will return it as a String instead of a Character.
If someone knows a better/more elegant solution, I would appreciate if you could share it.