I'm new to JPA (JPQL), so I got stacked with no good solution to maintain WHERE clause with a flexible comparison plan.
I would like to implement a JPQL statement of the form like:
SELECT i FROM Item i
WHERE i.weight (comparison_operator_placeholder) :weight
AND i.height (comparison_operator_placeholder) :height;
(comparison_operator_placeholder):{ = | < | > | <= | >= }
The (comparison_operator_placeholder) shall be selected during runtime, based on user's input.
Intuitively I realize that no such grammar exists, however, there shall be other way than writing queries for each combination. I will appreciate any workaround.
You don't have to write multiple queries. Just use a variable for the operator(s), like so:
String query = "SELECT i FROM Item i \n" +
"WHERE i.weight " + operator + " :weight \n" +
"AND i.height " + operator2 + " :height";
Related
Iam trying to use Jpa repository to get data from my sql server using a native query
This is a simple call from my service
repo.testData("h3","h3");
This is my repository query.
Select statement can read the binding variable :level but group by is unable to read it.
#Query(value="SELECT sum(pos.total_weekly_sales) as curr_yr_sales, sum(pos.total_weekly_qty) as curr_yr_qty, pos.vendor_nbr,pos.gmm_id,\n" +
"case \n" +
"when :level = 'h3' then pos.category_id\n" +
"else 0\n" +
"end category_id\n" +
"from dbo.agg_sams_data pos\n" +
"join dbo.calendar_dim cal on pos.wm_year_wk_nbr = cal.wm_year_wk_nbr\n" +
"WHERE \n" +
"cal.calendar_date BETWEEN '2019-09-11' and '2020-09-09'\n" +
"and pos.vendor_nbr = 68494\n" +
"and pos.gmm_id = 45\n" +
"and (:h3Flag = 'N' or pos.category_id = 52)\n" +
"GROUP by pos.vendor_nbr,pos.gmm_id,\n" +
"case \n" +
"when :level='h3' then pos.category_id\n" +
"else 0\n" +
"end",nativeQuery = true)
List<List<Double>> testData(String level,String h3Flag);
And i get the following error
com.microsoft.sqlserver.jdbc.SQLServerException: Column 'dbo.agg_sams_data.category_id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If i pass the hardcoded value in the group by clause it works fine(as below)
"GROUP by pos.vendor_nbr,pos.gmm_id,\n" +
"case \n" +
"when 'h3'='h3' then pos.category_id\n"
You should try putting pos.category_id into the group by instead of the whole case when statement. The problem is, that SQL Server can't be sure that the parameter in both cases will have the same value so the expressions could be different.
I have tried the following but not working.
String mainQuerySt = "select o.progressStatus, "
+ " CASE WHEN o.assigneeEmployee IS NOT NULL THEN o.assigneeEmployee.fullNameSt ELSE '' END as assignee "
+ " from Tt o"
em.createQuery(mainQuerySt).getResultList();
What is wrong with this? Actually, I want to show assigneeEmployee full name if it is not null and otherwise an empty string.
I am using EclipseLink v2.1 as JPA
Thanks in advnace.
EclipseLink Tutorial
You are using o.assigneeEmployee IS NOT NULL, and I assume o.assigneeEmployee is a relationship. Using dot notation forces an inner join, which then will filter out nulls. Try
String mainQuerySt = "select o.progressStatus, "
+ " CASE WHEN assigneeEmployee IS NOT NULL THEN assigneeEmployee.fullNameSt ELSE '' END as assignee "
+ " from Tt o left outer join o.assigneeEmployee assigneeEmployee"
If it does not return the results expected, then you will need to turn on SQL logging to see the SQL produced, and show the results you do expect.
I have a table with four columns, this is how it looks like. I would call it T_BPR_KPI_MONTHLY_VALUES
KPI_NAME_SHORT_S | MONTH_N | YEAR_N | VALUE_N
-----------------------------------------------
MY_KPI_1 | 1 | 2015 | 99.87
MY_KPI_2 | 1 | 2015 | 97.62
... | 1 | 2015 | ...
MY_KPI_1 | 2 | 2015 | ...
... | ... | 2015 | ...
Each kpi represents a measurement and each of them has daily values which are saved in another table called T_BPR_KPI_DY. My goal is to calculate and save monthly values of each KPI.
It is possible that on the certain day, daily values for some kpis are still missing and in order to precisely calculate monthly values I must be able to replace exisisting values in the database as well as insert new etries for the future months and years.
I tought that oracle sql merge operation would be good choice for this task. The idea is to check whether an entry already exists and if so than to update its value and if not to insert new one.
This is how the query looks like
MERGE INTO T_BPR_KPI_MONTHLY_VALUE A
USING( SELECT 'MY_KPI_1' AS KPI_NAME_SHORT_S, 1 AS MONTH_N, 2014 AS YEAR_N FROM DUAL ) B
ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S )
WHEN MATCHED THEN
UPDATE SET VALUE_N = ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014')
WHEN NOT MATCHED THEN
INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('MY_KPI_1', 1, 2014, ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014') )
I tought that calculating avg monthly values on the fly is not a bad idea, so as you can see I have another select query which only calculates avg monthy value for the specific kpi. I am not sure if this is a best practic solution but it works fine when I execute this query in oracle sql developer tool. however when I try to execute it from the app it does not work.
This is how the method looks like
public static void storeValuesToDb(ArrayList<String> kpiNames) throws SQLException {
Connection conn = getOracleJDBCConnection_DASH();
int currentYear = cal.get(Calendar.YEAR);
int startYear = cal.get(Calendar.YEAR) - 1;
for (String kpiName : kpiNames) {
for (int i = startYear; i <= currentYear; i++) {
for (int j = 0; j < 12; j++) {
try {
String myMergeSQL = ""
+ "MERGE INTO T_BPR_KPI_MONTHLY_VALUE A "
+ "USING( SELECT '" + kpiName + "' AS KPI_NAME_SHORT_S, " + (j + 1) + " AS MONTH_N, " + i + " AS YEAR_N FROM DUAL ) B ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S ) "
+ "WHEN MATCHED THEN "
+ "UPDATE SET VALUE_N = ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') "
+ "WHEN NOT MATCHED THEN "
+ "INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('" + kpiName + "', " + (j + 1) + ", " + i + ", ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') )";
System.out.println(myMergeSQL);
Statement stmt_dash = conn.createStatement();
stmt_dash.executeUpdate(myMergeSQL);
conn.commit();
stmt_dash.close();
} catch (SQLException ex) {
conn.close();
}
}
}
}
conn.close();
}
In terminal it prints out only the first merge sql. It neither finishs the operation nor throws an exception. It blocks somehow and in the db happens also nothing. It could be possible that my merge query is not correct or that it is not possible to execute this kind of operation with statement object. If someone is able to see what cases this issue, please help.
Thx in advance
I would start by reformulating your merge query and solve some issues:
the USING part of a MERGE actually means your "source of raw data". You are using a select from dual with hardcoded values. Here you should select all KPIs and also calculate the Average by KPI. Compose your query that selects all KPIs with their coresponding VALUE_N and use it in the USING part
when matched then UPDATE SET use the values from "source of raw data" which is alias B in your code, not compute on inside the UPDATE clause.
when not matched then INSERT VALUES - again, use values from "source of raw data" which is alias B in your code, do not try to compute the VALUE_N inside the insert - well at least not in that manner I think this is your querys main issue.
MERGE INTO xxx A using () B you gave 2 aliases to your tables but down the line inside the WHEN MATCHED or NOT you are not using the alias. This can raise problems if A and B have similar named columns.
An example of how I use merge in production:
Merge into Destination, using a select from a table Source (inside the select from source you can also add other computations obviously, in your case the average)
T_REPORT_DAILY_SNAPSHOT_2G should be in your code the select kpis name, value and average or whatever you need on INSERT and UPDATE
MERGE INTO T_CELLS_2G dest
USING (SELECT DISTINCT *
FROM T_REPORT_DAILY_SNAPSHOT_2G) src
ON (dest.lac = src.lac and dest.cell_id = src.cell_id)
WHEN MATCHED THEN
UPDATE SET
dest.cell_name = src.cell_name,
dest.loc_code = src.loc_code,
dest.site_code = src.site_code,
dest.rac = src.rac
WHEN NOT MATCHED THEN
INSERT (dest.cell_name,
dest.loc_code,
dest.site_code,
dest.lac,
dest.cell_id,
dest.rac)
VALUES (src.cell_name,
src.loc_code,
src.site_code,
src.lac,
src.cell_id,
src.rac);
Hope this helps in some way.
I posted a while back a question asking how to implement a search with the following criteria:
On the "Searching" page, the customer can search for movies by any of the following attributes or their combination (logical "AND" operation):
title;
year;
director;
star's first name and/or last name. This means you need to do both: a) first name or last name if only one of the two names is provided; b) first name and last name, if both names are provided.
I then have to spit back out information about that movie from my database given the search input. People suggested I use FullText Search, and I did. This is the solution I have come up with.
String searchInput = request.getParameter("searchInput");
String query = "";
// First see if there is a number in their search
// Note: doesn't consider more specific searches, because it only searches
// based on the given number
if (searchInput.matches("\\d+")) {
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(searchInput);
if (m.find()) {
String number = m.group();
int n = Integer.parseInt(number);
query = "SELECT title, year, director, id " +
"FROM movies " +
"WHERE year = " + n + " " +
"OR title LIKE '%" + n + "%'";
}
} else {
query = "SELECT title, year, director, r.id " +
"FROM movies AS r, stars AS s " +
"WHERE MATCH(title, director) AGAINST('" + searchInput + "*') " +
"OR MATCH(first_name, last_name) AGAINST('" + searchInput + "*') " +
"AND r.id IN (SELECT sim.movie_id " +
"FROM stars_in_movies AS sim " +
"WHERE sim.star_id = s.id " +
"AND sim.movie_id = r.id) " +
"GROUP BY title";
}
ResultSet rs = statement.executeQuery(query);
// Query again, this time ANDing the matches to see if
// there is a more specific search result
Statement statement2 = dbcon.createStatement();
query = "SELECT title, year, director, r.id " +
"FROM movies AS r, stars AS s " +
"WHERE MATCH(title, director) AGAINST('" + searchInput + "*') " +
"AND MATCH(first_name, last_name) AGAINST('" + searchInput + "*') " +
"AND r.id IN (SELECT sim.movie_id " +
"FROM stars_in_movies AS sim " +
"WHERE sim.star_id = s.id " +
"AND sim.movie_id = r.id) " +
"GROUP BY title";
ResultSet rs2 = statement2.executeQuery(query);
if (rs2.next()) {
// If there is a more specific match (ANDing the matches), use that instead
rs2.beforeFirst();
rs = rs2;
}
Now, I know this is REALLY bad code, since I'm executing almost the same query twice, with the only difference being that I AND the matches in the second query. I can't seem to figure out a way to combine the two queries or also reduce the amount of matching done to speed up the query. I also don't have a good way to deal with the year of the movie that the user may or may not input.
Here is what I have tried:
I have tried searching up the FullText Search documentation for some functions that can be included in my search to make my SQL query return the more specific result if it matches more things, but the only thing that comes closest to such a thing is the Boolean Full-Text Search. However, I don't think I can use such a function because I have to distinguish between the different user's keyword inputs.
I have also tried Googling AND/OR operations in MySQL that allows AND when possible, and if not possible, then ORs the query. However, it doesn't seem possible to do this.
UPDATE: I have nested subqueries now and the search is MUCH faster. However, I still need help with improving my code to return the more specific match and only the more specific match, if it exists. Else, return the more general match (if anything at all matches).
Run each sub query separately. You can AND and OR results in code as required, assuming the size of the results are reasonable. Bit more coding, but you only run the necessary queries once.
I am using Spring 3.1.1 with Hibernate 4 and a MSSQL database. Finally, I have been able to query my database with joins in my table that returns the correct answers. Although, it seems that it does not return the entire strings of my messages, but cuts at 29/30 digits. Here is my query:
SQLQuery sql = this.getCurrentSession().createSQLQuery(
"SELECT event.id as eventid, CAST(event_type.type_name AS varchar) as eventtype, event.check_date, event.event_date, event.status, CAST(event_message.message AS varchar) as eventmessage " +
"FROM event_log event " +
"LEFT JOIN event_type " +
"ON (event.event_type_id = event_type.id) " +
"LEFT JOIN event_message " +
"ON (event.event_message_id = event_message.id) " +
"WHERE event.event_type_id = " + jobId +
"ORDER BY eventid");
The result can be:
4506 Database 2014-01-15 14:14:15.02 2014-01-15 14:14:15.02 false Database-connection cannot be
Where the columns are id, task_name, check_date, event_date, status and the message-string at the end. .
The result goes to a ArrayList<Object[]> where I read row[0] etc. to get the entities in the row. The string message gets cut after 29 digits, which is disturbing. Why does it cut the string and how can I fix this? In my database the string exists in it's full and is entitled 400 characters max.
I know this is probably not the best way to query my database, but it will do for my application since it works.
You are using varchar without a length. Never do this!
Replace:
CAST(event_type.type_name AS varchar)
With something like:
CAST(event_type.type_name AS varchar(255))
In some contexts, the default length is 32. In some it is 1. In general, though, always specify the length.
EDIT:
Actually, you probably don't even need the cast. Why not just have event_type.type_name in the select list?