Using sub-select in FROM clause inside JPA #NamedQuery - sql

In my app I need to use #NamedQuery to find the type of the most frequent operation assigned to specific account
#Entity
#Table(name="\"ACCOUNTOPERATION\"")
#NamedQuery(name="AccountOperation.findTypeOfMostFrequentOperation", query="" +
"SELECT ao.type from AccountOperation ao WHERE ao.account.id = ?1 " +
"GROUP BY ao.type HAVING COUNT(ao) = (" +
"SELECT MAX(typeCountQuery.typeCount) " +
"FROM (" +
"SELECT COUNT(aop) as typeCount " +
"FROM AccountOperation aop WHERE aop.account.id = ?1 GROUP BY aop.type" +
") as typeCountQuery" +
")"
)
public class AccountOperation {
#ManyToOne
private Account account;
private BigDecimal amount;
private OperationType type;
...
Right after FROM clause at '(' character, which begins typeCountQuery's body I'm getting
')', ',', GROUP, HAVING, IN, WHERE or identifier expected, got '('
I've read that JPA does not support sub-selects in the FROM clause, so is there any way to rewrite SQL code to still use it in #NamedQuery?
I'm using IntelliJ IDE with H2 DB and with eclipselink and javax.persistence in dependencies.

Link to source
In JPQL, you cannot use subqueries. To resolve this issue, you need to use some keywords like ALL, ANY, which work similiar.
So in your situation it could be:
#NamedQuery(name="AccountOperation.findTypeOfMostFrequentOperation", query="" +
"SELECT ao.type from AccountOperation ao WHERE ao.account.id = ?1 " +
"GROUP BY ao.type HAVING COUNT(ao) >= ALL (" +
"SELECT COUNT(aop) as typeCount " +
"FROM AccountOperation aop WHERE aop.account.id = ?1 GROUP BY aop.type)"

The type with a highest count returns the following query
select type
from AccountOperation
where id = ?
group by type
order by count(*) desc
fetch first 1 ROWS only
You should be anyway avare of the existence of ties, i.e. more types with the identical maximal count and should make some thought how to handle them.
I.e. in Oracle you may say fetch first 1 ROWS WITH TIES to get all the types with tha maximal count.

Related

how can join two query in PostgreSQL

I want to join two querys into one query.
What retrieved in the first query is a tables with column of resourceindex that sorts ascending:
String loadRates = "SELECT * FROM ratings WHERE userindex="
+ uindex
+ " ORDER BY rank DESC";
And in the second query, what should retrieved is rows of resourceindexes:
String loadResources = "SELECT * FROM resourceinfo WHERE resourceindex = "
+ rs.getInt("resourceindex");
How can I combine these into a single query?
Do not use old style join but use the keyword join.
Never ever write an SQL string like that with concatenation of parameters but use parameters instead.
"SELECT * FROM public.resourceinfo"
+ " inner join public.ratings ON ratings.resourceindex = resourceinfo.index"
+ " WHERE ratings.userindex = $1" +
+ " ORDER BY ratings.rank DESC;";
How you would apply the parameters depend on the language you are using which you didn't tag.
EDIT: If you meant it would also filtered by a resourceindex parameter then add it too as:
AND resourceinfo.index = $2

Unable to run CASE WHEN for array in Spring Boot repository

I am trying to run this
#Query(value = "SELECT t.*\n" +
"FROM tlm_trade t\n" +
"left join tlm_trade_nugget n\n" +
"on t.trade_nugget_id=n.id\n" +
"where \n" +
"(year(t.created_date) in (case when ?1 is null then year(t.created_date) else ?1 end))\n" +
";", nativeQuery = true)
List<TlmTrade> findByFilter(Set<Integer> year);
;
When I pass this as the argument
[2021,2020,2019]
Got this error instead
SQLException: Operand should contain 1 column(s)
Any idea what's wrong?
case when ... end is an expression, they evaluate to a single value.
In your example you are hoping that you could use a case expression to act as "dynamic sql". i.e. if ?1 is null, then insert the term "year(t.created_date)" but if ?1 is not null then insert ?1
You cannot do that.
It would make more sense to do this by branching before forming #Query, one branch for ?1 being null, and another which uses ?1
Also, avoid case expressions in where clauses, it is better to express filters in binary terms such as this, using parentheses where needed:
"where (?1 IS NULL AND year(t.created_date) = '2021' )\n"
?1 by the way will need to equal something, or be greater than something etc. ?1 by itself will not form a filtering condition.

HSQLDB - updatable statements don't work with "SELECT TOP" or "ORDER BY"

I'm using HSQLDB and preparedStatements just fine, but if I include either "SELECT TOP" or "ORDER BY" in my SQL statement, when I call updateBoolean (or UpdateInt, etc), I hit an exception:
java.sql.SQLException: attempt to assign to non-updatable column
This sample code works fine:
preparedStatement = connection.prepareUpdatable(
"SELECT " + MyTable.COL_ID + ", " +
MyTable.COL_READ +
" FROM " + MyTable.NAME +
" WHERE " + MyTable.COL_LOCAL +
" =? AND " + MyTable.COL_REMOTE +
" =?",
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE);
preparedStatement.setString(1, localAddress);
preparedStatement.setString(2, remoteAddress);
ResultSet rs = connection.query(preparedStatement);
if (rs.next())
{
rs.updateBoolean(MyTable.COL_READ, isRead);
rs.updateRow();
}
I get the exception if I change "SELECT" to "SELECT TOP". Or if I append this to the SQL statement:
" ORDER BY " + MyTable.COL_RECEIVED_TIMESTAMP + " DESC"
Thanks for any help.
NickB
An updatable SELECT statement cannot have TOP n, LIMIT or ORDER BY. This restriction is imposed by the SQL standard. Your SELECT becomes not-updatable when you add one of those keywords.
It is possible to use a subquery in a WITH clause with the above keywords and the SELECT is updatable.
CREATE TABLE t (a int, b int, PRIMARY KEY(a));
WITH SUBQ(COL) AS (SELECT TOP 1 a FROM t)
SELECT * FROM t WHERE a IN (SELECT * FROM SUBQ)

ebean query with subquery

I'm trying to do using EBean the equivalent of
select * from myTable1 where id not in (select id2 from myTable2) ;
I have no reference of table1 Object in table2 Object and the same the other way around.
Does anyone knows how to that using EBean ?
For the moment all I have is :
List<MyTable1> myResult = MyTable1.find.where().eq("id","1" ).findList();
Thanks.
C.C.
Apparently it has been possible to do this since 2009 using the example given in this bug report:
http://www.avaje.org/bugdetail-92.html
The example:
Query<Product> subQuery =
Ebean.createQuery(Product.class)
.select("sku")
.where().idEq(4).query();
List<MinCustomer> list = Ebean.find(MinCustomer.class)
.where().in("name", subQuery)
.findList();
However:
I am unable to make it work because the SQL generated is invalid. Seemingly due to a string replacement happening behind the scene in Ebean where (for me at least) the table name in the subquery is lost.
I expect it has to do with my main query includes a reference to the table from which my subquery "is selecting".
Turning the valid SQL from the example:
select c.id, c.name, c.notes
from o_customer c
where (c.name) in (select p.sku from o_product p where p.id = ? )
... into this invalid SQL in my case:
select t0.id as c0, ... t0.location_id as c8
from myRecordClass t0
where (t0.location_id) in (
select t0.id
from t0.location_id t0 # should be: from location t0
where t0.customer_id = ?
) and t0.creation > ?
order by t0.creation desc
The workaround:
Use the RawSql approach like in https://stackoverflow.com/a/27431625/190599 - example here:
String sql = "select b.id, b.location_id ... " +
"from myRecordClass b " +
"where location_id in (" +
"select id " +
"from location " +
"where customer_id = " + user.getCustomer().getId() +
") " +
"order by creation desc limit 10";
RawSql rawSql = RawSqlBuilder
.parse(sql)
.columnMapping("b.id", "id")
.columnMapping("b.location_id", "location.id")
....
.create();
Query<MyRecordClass> query = Ebean.find(MyRecordClass.class);
query.setRawSql(rawSql);
final List<MyRecordClass> list = query.findList();
I hardly believe that RawSql is fastest way to achieve this kind of query, it allows you to return list of mapped objects.
It's also possible to use SqlQuery (described in Reference guide (PDF)) to fetch a list of SqlRows - so you can find required data without any mapping at all.

Sorting null-data last in database query

i have an application where i order make a query to my database with an ORDER BY clause, it will order them in alphabetical order. i only have one small problem, it happens fairly often that one of the strings that the query is ordering by contains nothing (string="") when sorting in alphabetical order these get populated at the top of the list infront om a,b,c... i plain and simple dont want this. after a lot of googling i found on an oracle forum that i should change the SORT BY part of the query to "SORT BY xxx ASC NULLS LAST" this caused a fatal error when querying.
how shall i go by fixing this seemingly small issue?
here is my query statement as is today.
public Cursor fetchAllDatesByTag() {
return mdiktationsDb.rawQuery("SELECT " + KEY_DATEID + "," +" " + KEY_DATE + "," + " " + KEY_TIME + "," + " " + KEY_DICTTAG + "," + " " + KEY_DICTLISTIMAGE + " FROM " + DATABASE_TABLE + " ORDER BY " + KEY_DICTTAG + " ASC", null);
}
use a CASE equivalent in your ORDER BY
Like
ORDER BY CASE column WHEN NULL THEN 1 ELSE 0 END, column
so then it orders by the nulls first, then the actual column.
EDIT: And if you want to filter ""s (blank strings) or whatever else, you can employ this same method... assigning a numeric value to it and sorting before the alphabetizing.
EDIT2:
....+ " ORDER BY CASE " + KEY_DICTTAG + "WHEN NULL THEN 1 ELSE 0 END, " + KEY_DICTTAG + " ASC"
Solution one:
ORDER BY foo NULLS LAST
and
ORDER BY foo NULLS FIRST
But this seems to work only with numeric columns :(
Solution two:
ORDER BY IF(ISNULL(my_field),1,0),my_field
which will create a "fake"-column that just consist 0 or 1, depending on if my_field is null or not null, and sort on that. the fields that are not null will come first. in a second step, SQL will sort my_field, like before.
How about adding a WHERE field IS NOT NULL. This way you shouldn't get any null values back from your query.