I'm new to JDOQL and I'm having troubles with the below. I'm trying to get the average salary for the department and then select the departments where the average salary is higher than a certain value.
Query averageSalaryByDep = pm.newQuery(Employee.class);
averageSalaryByDep.setResult("department, avg(salary)");
averageSalaryByDep.setGrouping("department");
Query qry = pm.newQuery(Department.class);
qry.setFilter("this.name == dep.name && averageSalary > 10000");
qry.declareVariables("Department dep, double averageSalary");
qry.addSubquery(averageSalaryByDep, "Department dep, double averageSalary", null);
The error message I'm currently getting:
javax.jdo.JDOUserException: Class name averageSalary could not be resolved
at org.datanucleus.api.jdo.NucleusJDOHelper.getJDOExceptionForNucleusException(NucleusJDOHelper.java:636)
at org.datanucleus.api.jdo.JDOQuery.executeInternal(JDOQuery.java:391)
at org.datanucleus.api.jdo.JDOQuery.execute(JDOQuery.java:216)
A subquery has a single variable name (and returns a single thing). If unsure about something, put what the single-string query would look like (and the resultant SQL) and then it ought to be clear. The JDO spec has some useful examples IIRC
Regarding what you wanted to retrieve, I'd suggest you look at something more like
Query averageSalarySubq = pm.newQuery(Employee.class);
averageSalarySubq.setResult("avg(salary)");
averageSalarySubq.setFilter("this.department = :outerDepartment");
Query qry = pm.newQuery(Department.class);
qry.setFilter("averageSalary > 10000");
qry.declareVariables("double averageSalary");
qry.addSubquery(averageSalarySubq, "double averageSalary", null, "this");
which would equate to something like
SELECT FROM mydomain.Department WHERE
(SELECT AVG(e.salary) FROM mydomain.Employee e WHERE e.department = this) > 10000
hence subquery gets the average salary but joined to the outer query Department. Defining the SQL would reveal to you whether that is what you intend or not, but either way a subquery is for a single variable
Related
I'm new to ORM interface, and I'm trying to connect to my databases with Hibernate.
What I've figured out so far is:
With a serializable object, I can get a persistent object with
Person p = session.get(Person.class, serializable);
I can get all the objects by a list with
List people = session.createQuery("FROM Person").list();
What I need is to find a row that meets a certain condition, such as SELECT * FROM person WHERE name="Kim" AND age=30;
However, the above two aren't the ways to achieve this.
#Entity
#Table(name = "person")
public class Person {
#Id
private Integer id; // I can use this variable when using session.get(Person.class, serializable) , but I cannot know the id of my target row.
private String name;
private Integer age;
...
Should I iterate all the objects in people, and check whether all the member variables match what I want?
Is there any simple way to achieve this?
First and most importantly, never put user input in a query like this
SELECT * FROM person WHERE name="Kim" AND age=30;
You have to use Prepared Statements. Learn why from Bobby Tables.
Secondly, you should use the JPA interface EntityManager instead of Hibernate's Session as the second one anchors you to a specific implementation, rather than the wider standard.
With the EntityManager you get an object by id like this:
Person p = em.find(Person.class, id);
To get a list of People you can create a JPQL query like this:
TypedQuery<Person> query = em.createQuery("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age", Person.class);
query.setParameter("name", "Kim"); // :param1 defines a parameter named "param1" in the query
query.setParameter("age", 30);
List<Person> results = query.getResultList();
You could also do this in one chain if you don't need to reuse the query with different parameters on a loop.
List<Person> results = em.createQuery(..., Person.class)
.setParameter("name", "Kim")
.setParameter("age", 30)
.getResultList();
The reason to put every call on a new row is in case an exception occurs it will give you the proper row to look for. If they're all in one row, then that's not very useful.
If your query is a SELECT, and it needs to return exactly one result every time, you can use getSingleResult() instead of getResultList(). If you do that and the query did return more than one result, it will throw a NonUniqueResultException. If the query did not return any results it will throw a NoResultException instead of returning null.
If your query is NOT a SELECT, then you have to use executeUpdate() to invoke it after setting the parameters.
There are many resources to get you started, but generally if its for a Hibernate version before 5.2 you should consider it outdated, and it will likely be more difficult.
This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id
I cannot find a solution to a problem that seems to be easy. Say there are 2 entity classes:
class A {
Set<B> bs;
}
class B {
String text;
}
How to create a criteria query that returns all A's that contains at least one B entity which fulfills a given condition (like b.text = 'condition')?
I think this link can be useful:
http://mikedesjardins.net/2008/09/22/hibernate-criteria-subqueries-exists/
It contains the following example about how create n exists criteria:
"What you’re really trying to do is to obtain all Pizza Orders where an associated small pizza exists. In other words, the SQL query that you’re trying to emulate is
SELECT *
FROM PIZZA_ORDER
WHERE EXISTS (SELECT 1
FROM PIZZA
WHERE PIZZA.pizza_size_id = 1
AND PIZZA.pizza_order_id = PIZZA_ORDER.pizza_order_id)
The way that you do that is by using an “exists” Subquery, like this:
Criteria criteria = Criteria.forClass(PizzaOrder.class,"pizzaOrder");
DetachedCriteria sizeCriteria = DetachedCriteria.forClass(Pizza.class,"pizza");
sizeCriteria.add("pizza_size_id",1);
sizeCriteria.add(Property.forName("pizza.pizza_order_id").eqProperty("pizzaOrder.pizza_order_id"));
criteria.add(Subqueries.exists(sizeCriteria.setProjection(Projections.property("pizza.id"))));
List<pizzaOrder> ordersWithOneSmallPizza = criteria.list();
And voila, the result will contain two PizzaOrders!"
I'm trying to create a query using CriteriaBuilder to select all Product with a stock greater than zero. Stock is sum(DeliveryRow.amount) - sum(DispatchRow.amount). Both ofcourse only containing the right Product.
I have tried creating Subquery for both DeliveryRow and DispatchRow though I feel like this should be done using a join().
Classes
Product {
(...)
}
DeliveryRow {
#ManyToOne
private Product product;
private int amount;
}
DispatchRow {
#ManyToOne
private Product product;
private int amount;
}
Query
In this query I'm not sure how to handle the xxx. I've tried making to subqueries but that didn't work out.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root product = query.from(Product.class);
query.select(product);
// sum of DeliveryRow.amount where DeliveryRow.product = Product
// minus
// sum of DispatchRow.amount where DispatchRow.product = Product
Expression stock = xxx;
query.where(cb.gt(stock, Integer.parseInt(0)));
return em.createQuery(query).getResultList();
Any suggestions on how to solve this?
I have recently been doing research on JPA/JPQL, studying the three different approaches for retrieving entities: NamedQueries, em.CreateQuery and CriteriaBuilder. The CriteriaBuilder in my opinion is the most awkward of the three to use. I would recommend creating a NamedQuery to handle this situation, it will be a lot easier to implement and read.
Using this JPQL expression you could retrieve all of the products with a stock greater than zero:
SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0
/* This assumes product has a Collection<DispatchRow> named dispatchRows
and a Collection<DeliveryRow> named deliveryRows.
*/
Make this a named query in the `Product' entity
//This should be concatenated or on one line
#NamedQuery(name="Product.hasStock"
query="SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0");
Then execute this query with an EntityManager
#PersistenceContext
EntityManager em;
public void execute(){
List<Object[]> products =
em.createNamedQuery("Product.hasStock").getResultList();
/* Projections return a List<Object[]> where position 1 in the object array
corresponds with the first field in the select statement, position two
corresponds with the second field and so on... These can also be strongly typed
if an object is created and the constructor is specified in JPQL statement
*/
}
I know this is a different approach than using the Criteria API, but in my opinion JPQL queries are vastly superior to the Criteria API. Compared to the JPQL syntax, which is very similar to SQL the API felt less concise and intuitive. If you decide to take this route, I have created a video tutorial that demonstrates #NamedQueries and shows how to strongly type the results of queries containing projections. It can be found here.
I am using the Spring Framework's StoredProcedure (I am extending it, of course) to get a result set and an output parameter (#totalRowsReturned) which is an Integer. The problem is that when the resultset being returned is supposed to be an empty list, I am getting a NullPointerException when I try to retrieve the output parameter (totalRows, which naively I would expect it to be zero).
I would like to mention that the code works fine when the result set being found is not empty.
My questions are:
Why isn't #totalRowsReturned being set to zero in this case? (Or in case it is, why can't I retrieve it through the Java code?)
How can I make this code (Java code + T-SQL code) work in such a way that #totalRowsReturned will be set to zero when required, and I could retrieve it through the Java code?
Dao:
List<Book> books = null;
int totalRows = 0;
Map<String, Object> results = storedProcedure.execute(parameters);
books = (List<Book>) results.get("rs");
totalRows = (Integer) results.get("totalRowsReturned"); // NullPointerException on this line if total rows are supposed to be zero!!
T-SQL stored procedure:
CREATE PROCEDURE Find_Books
#authorName Varchar(250),
#totalRowsReturned INTEGER OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SelectQuery NVARCHAR(2000)
SET #SelectQuery = 'SELECT #totalRows=COUNT(*) OVER() FROM book b WHERE b.author_name = #authorName'
Execute sp_Executesql #SelectQuery, N'#authorName VARCHAR(250), #totalRows int OUTPUT', #authorName, #totalRows=#totalRowsReturned OUTPUT
-- Select resultset goes here...
END
UPDATE:
Actually, my stored procedure looks more like this (the change is the additional #first_id = b.book_id in the SELECT):
CREATE PROCEDURE Find_Books
#authorName Varchar(250),
#totalRowsReturned INTEGER OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SelectQuery NVARCHAR(2000)
DECLARE #first_id int
DECLARE #first_id_local_returned int
SET #SelectQuery = 'SELECT #first_id = b.book_id, #totalRows=COUNT(*) OVER() FROM book b WHERE b.author_name = #authorName'
Execute sp_Executesql #SelectQuery, N'#authorName VARCHAR(250), #first_id int OUTPUT, #totalRows int OUTPUT', #authorName, #first_id=#first_id_local_returned OUTPUT, #totalRows=#totalRowsReturned OUTPUT
-- Select resultset goes here... I am using the value of #first_id_local_returned in this SELECT...
END
The problem is, that when there are no rows returned from the SELECT, b.book_id is not defined, so I get an org.springframework.dao.TransientDataAccessResourceException ... Column 'book.book_id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
So it seems like if I keep the OVER(), then #totalRows=COUNT(*) OVER() fails when there are zero rows returned, and if I remove the OVER(), then #first_id = b.book_id fails.
Any idea how I overcome this?
COUNT(*) OVER () is not the correct thing to use here. Just use COUNT(*)
COUNT(*) OVER () returns a result set with as many rows as the COUNT. e.g if the result is 3 the result set will be
3
3
3
The effect of your query is then to repeatedly re-assign the value 3 to the #totalRows variable as many times as there are rows which is completely pointless.
Conversely if COUNT(*) = 0 then the COUNT(*) OVER () result set is empty so your variable is never assigned to at all.
COUNT(*) will always give you a single row scalar resultset here that you can assign to the variable and will have a more efficient execution plan without unnecessary common subexpression spools too.
Edit
In response to your question in the comments. This does the same thing as your linked article. It can use a narrower index to find the (say) 10,000th Employee then joins onto Department only for the 1 subsequent page of records. This paging method only works correctly because each employee has exactly one department.
WITH E1(RN, EmployeeID)
AS (SELECT ROW_NUMBER() OVER (ORDER BY EmployeeID),
EmployeeID
FROM Employees)
SELECT TOP (#maximumRows) e.*,
d.Name AS DepartmentName
FROM Employees e
INNER JOIN Departments d
ON e.DepartmentID = d.DepartmentID
WHERE EmployeeID >= (SELECT EmployeeID
FROM E1
WHERE RN = #startRowIndex)
ORDER BY e.EmployeeID
You need to use an aggregate function on b.book_id.
You can use min()
SELECT #first_id = min(b.book_id), #totalRows=COUNT(*) FROM book ...
or max() if that is more appropriate in your case
SELECT #first_id = max(b.book_id), #totalRows=COUNT(*) FROM book ...
I'm not 100% sure what you're trying to do, but I think it's something like this (you really don't want/need dynamic SQL in this case):
CREATE PROCEDURE Find_Books
#authorName Varchar(250),
#totalRowsReturned INTEGER OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET #totalRowsReturned = SELECT COUNT(*)
FROM book b WHERE b.author_name = #authorName
SELECT b.book_id
FROM book b WHERE b.author_name = #authorName
END