Join two, three related tables by HQL - java

I have three related tables like below:
+-------------+---------+------------+
| customer_id | name | surname |
+-------------+---------+------------+
| 1 | Jan | Bielecki |
| 2 | Adam | Bielen |
.....
+----------+--------+---------------------+-------------+
| order_id | amount | date | customer_id |
+----------+--------+---------------------+-------------+
| 1 | 10.23 | 2017-02-15 00:00:00 | 1 |
| 2 | 20.56 | 2017-02-16 00:00:00 | 1 |
| 3 | 30.57 | 2017-02-17 00:00:00 | 2 |
| 4 | 40.52 | 2017-02-18 00:00:00 | 2 |
| 5 | 50.30 | 2017-02-19 00:00:00 | 1 |
.....
+-----------------+-----------+------------+----------+
| order_detail_id | item_name | item_price | order_id |
+-----------------+-----------+------------+----------+
| 1 | item 1 | 2.00 | 1 |
| 2 | item 2 | 2.50 | 1 |
| 3 | item 3 | 3.00 | 1 |
| 4 | item 4 | 4.00 | 2 |
| 5 | item 5 | 5.50 | 2 |
| 6 | item 6 | 7.60 | 3 |
| 7 | item 7 | 5.00 | 3 |
| 8 | item 8 | 3.00 | 4 |
| 9 | item 9 | 7.00 | 4 |
| 10 | item 10 | 8.00 | 4 |
| 11 | item 11 | 2.00 | 5 |
| 12 | item 12 | 2.50 | 5 |
.....
Firstly i'm fighting with connect first and second table. For connect surnames with sum of amount.
I'm trying like this:
select sum(o.amount) as totalSum
from Order as o,
Customer as c
join c.surname as surname
where c.orders:=o.customer
group by o.customer
order by sum(o.amount) desc
with changing to many ways this section: where c.orders:=o.customer
The most common error is NullPointerException.
Before done this in SQL:
Table customer_id <-> total_amount
SELECT customer_id,
SUM(amount) as total_amount,
COUNT(amount) as orders_quantity
FROM softhis_db.orders
GROUP BY customer_id;
Table customer_id <-> 3 most exp. orders + dates
SELECT orders.customer_id, orders.amount, orders.date
FROM orders_details
RIGHT JOIN orders
ON orders.order_id = orders_details.order_id
ORDER BY amount DESC
LIMIT 3;
Customer:
#Entity
#Table(name = "customers")
public class Customer {
#Id
#Column(name = "customer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name", length = 50)
private String name;
#Column(name = "surname", length = 50)
private String surname;
#OneToMany(mappedBy = "customer")
private Set<Order> orders = new HashSet<>();
Order:
#Entity
#Table(name = "orders")
public class Order {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "date")
private Date date;
#Digits(integer = 5, fraction = 2)
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
#OneToMany(mappedBy = "order")
private Set<OrderDetail> ordersDetails = new HashSet<>();
OrderDetail:
#Entity
#Table(name = "orders_details")
public class OrderDetail {
#Id
#Column(name = "order_detail_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Lon id;
#Column(name = "item_name", length = 50)
private String itemName;
#Digits(integer = 5, fraction = 2)
#Column(name = "item_price")
private BigDecimal itemPrice;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private Order order;
The point is how to do this properly in HQL ? Next step will be searching by surname and getting result like 'my target'.
My target is:
+---------+---------------+
| surname | sum of amount |
+---------+---------------+
|Bielecki | 150.40 |
|Bielen | 130.34 |
......
+-----------------------------------+--------------------+
| surname | 3 most expensive orders | date |
+-----------------------------------+--------------------+
|Bielecki | 120.23 |2017-02-15 00:00:00 |
|Bielecki | 80.20 |2017-02-18 00:00:00 |
|Bielecki | 20.20 |2017-02-19 00:00:00 |
+---------+-------------------------+--------------------+
|Bielen | 190.23 |2017-02-15 00:00:00 |
|Bielen | 80.20 |2017-02-18 00:00:00 |
|Bielen | 20.20 |2017-02-19 00:00:00 |
+---------+-------------------------+--------------------+
.....

Try these queries
SELECT
customers.surname
, SUM(amount) "sum of amount"
FROM
customers
INNER JOIN
orders
ON
customers.customer_id = orders.customer_id
GROUP BY
customers.surname
ORDER BY
customers.surname ASC
For the 3 most expensive orders per surname you need to use user variables to create ranking.
And filter on that ranking.
SELECT
customers.surname
, orders_ranked.amount AS "3 most expensive orders"
, orders_ranked.date
FROM (
SELECT
*
, (
CASE
WHEN
#customer_id = orders.customer_id
THEN
#rank := #rank + 1
ELSE
#rank := 1
END
)
AS
rank
, #customer_id := orders.customer_id
FROM
orders
CROSS JOIN (
SELECT
#customer_id := 0
, #rank := 0
)
AS
init_user_variables
ORDER BY
orders.customer_id ASC
, orders.amount DESC
)
AS
orders_ranked
INNER JOIN
customers
ON
orders_ranked.customer_id = customers.customer_id
WHERE
orders_ranked.rank <= 3

I figured out how to translate those SQL queries to HQL.
In order:
1.
select o.customer.surname, sum(o.amount) as s from Order as o group by o.customer
2.
select o.customer.surname, o.amount, o.date from Order as o, OrderDetail as od

Related

Hibernate envers and ElementCollection

I have a simple program with a Resource and List of ItemSelection like this
#Audited
#Table(name = "resource_item")
public class Resource {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Access(AccessType.PROPERTY)
private Long id;
private String nom;
#ElementCollection(targetClass = ItemSelection.class)
#CollectionTable(name = "item_selection", joinColumns = #JoinColumn(name = "resource_id"))
#OrderColumn(name = "list_order")
private final List<ItemSelection> itemSelections = new ArrayList<>();
...
...
#Embeddable
#Access(AccessType.FIELD)
public class ItemSelection {
//#Column(name = "hierarchy_code", nullable = false)
private String hierarchyCode;
//#Column(name = "hierarchy_level", nullable = false)
private int hierarchyLevel;
//#Column(name = "hierarchy_value_code", nullable = false)
private String hierarchyValueCode;
...
...
When i m adding resource with 3 elements in itemSelections, hibernate envers log correctly:
localhost sa#envers=# select * from item_selection_aud ;
rev | revtype | resource_id | list_order | hierarchy_value_code | hierarchy_code | hierarchy_level
-----+---------+-------------+------------+----------------------+----------------+-----------------
2 | 0 | 1 | 1 | hvalueCode3 | hcode3 | 3
2 | 0 | 1 | 0 | hvalueCode1 | hcode1 | 1
2 | 0 | 1 | 2 | hvalueCode2 | hcode2 | 2
(3 rows)
when i m updating one line in item_selection, i only see one update in sql but hibernate envers log this:
localhost sa#envers=# select * from item_selection_aud ;
rev | revtype | resource_id | list_order | hierarchy_value_code | hierarchy_code | hierarchy_level
-----+---------+-------------+------------+----------------------+----------------+-----------------
2 | 0 | 1 | 1 | hvalueCode3 | hcode3 | 3
2 | 0 | 1 | 0 | hvalueCode1 | hcode1 | 1
2 | 0 | 1 | 2 | hvalueCode2 | hcode2 | 2
3 | 0 | 1 | 0 | hvalueCode1 | CODE | 1
3 | 2 | 1 | 0 | hvalueCode1 | hcode1 | 1
(5 rows)
as you can see, i have a revtype 2 and 0 for the modified line instead of revtype 1 for an update.
I'have try to use #Column(name = "hierarchy_code", nullable = false) but nothing change.
For now my only solution is to transform #ElementCollection into #OneToMany, but I have many #ElementCollection in my application so too many refactoring...
Did I miss something in the configuration? I am using DefaultAuditStrategy, same behavior with ValidityAuditStrategy that only add revend column. Hibernate-envers 5.6.12-Final

JPA nested inner join query

I have 3 tables and a ManyToMany relationship between usuario and projeto, and I'm having a headche to make it search accordly :
usuario
+-------------+-------------+
| idMatricula | nomeUsuario |
+-------------+-------------+
| 1 | Victor |
| 12345 | Juquinha |
| 2 | Nilton |
| 3 | Fatima |
| 4 | Bianca |
| 4422 | Pedrinho |
+-------------+-------------+
projeto
+-----------+----------------+--------------+
| idprojeto | nomeProjeto | faseProjeto |
+-----------+----------------+--------------+
| 1 | APS_6_semestre | Em Andamento |
| 2 | APS_7_semestre | Em testes |
| 3 | APS_8_semestre | Em testes |
| 4 | TCC | Concluido |
+-----------+----------------+--------------+
projeto_has_usuario
+-------------------+---------------------+
| projeto_idprojeto | usuario_idMatricula |
+-------------------+---------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
+-------------------+---------------------+
I'm trying to query only the projects associated to the user, but my query doesnt work and I dont know if it is a semantic error or an annotation error. Here is my mapped class:
Projeto:
#Entity
#Table(name="projeto")
public class Projeto {
#GeneratedValue
#Id
#Column(name="idprojeto", nullable = false)
private int idprojeto;
#Column(name="nomeProjeto", nullable = false)
private String nomeProjeto;
#Column(name="faseProjeto", nullable = false)
private String faseProjeto;
#ManyToMany(mappedBy="projetos")
private List<Usuario> usuarios;
Usuario:
#Entity
#Table(name="usuario")
public class Usuario {
#Id
#Column(name="idMatricula", nullable = false)
private String id;
#Column(name="nomeUsuario", nullable = false)
private String nomeUsuario;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projeto_has_usuario",
joinColumns={#JoinColumn(name="usuario_idMatricula")},
inverseJoinColumns={#JoinColumn(name="projeto_idprojeto")})
private List<Projeto> projetos;
Here is my query:
TypedQuery<Usuario> listaUsuario = manager.createQuery("SELECT u FROM Usuario as u", Usuario.class);
// Some validation ..
TypedQuery<Projeto> listaTabela = manager.createQuery("SELECT p FROM projeto p "
+ "JOIN p.usuarios u WHERE p.idprojeto MEMBER OF u.projetos", Projeto.class);
for (Projeto projetos : listaTabela.getResultList()) {
System.out.println("Nome: " + projetos.getNomeProjeto() + "\nID: " + projetos.getIdprojeto()
+ "\nFase: " + projetos.getFaseProjeto());
Returns me:
Nome: APS_6_semestre
ID: 1
Fase: Em Andamento
I'm using Eclipse IDE 4.7.2, Hibernate 5.2.12 and JPA 2.1.

Query Predicate in QueryDSL

The environment is Java, Spring-boot, Hibernat, QueryDSL, MySQL.
I have table structure
Episode
+----+-------------+--------
| id | address_id | eventno
+----+-------------+--------
| 5 | 27 | F123
| 6 | 30 | F456
| 7 | 45 | F789
+----+-------------+--------
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String eventno;
#ManyToOne(cascade = CascadeType.ALL)
private Address address;
Episode_Person
+----+--------------+--------------+------------+-----------+
| id | episode_role | primary_flag | episode_id | person_id |
+----+--------------+--------------+------------+-----------+
| 19 | Buyer | | 5 | 1 |
| 20 | Subject | | 5 | 2 |
| 23 | Witness | | 6 | 3 |
| 24 | Child | | 6 | 4 |
| 27 | Buyer | | 5 | 3 |
| 63 | Investor | | 5 | 4 |
| 64 | Subject | | 7 | 1 |
| 65 | Subject | | 7 | 3 |
#Entity
public class EpisodePerson {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#Valid
private Person person;
#ManyToOne
private Episode episode;
Person
+----+-----------+----------+
| id | firstname | surname |
+----+-----------+----------+
| 1 | Clint | eastwood |
| 2 | Angelina | joilee |
| 3 | Brad | pitt |
| 4 | Jennifer | aniston |
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {"nia"}))
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String surname;
private String firstname;
private String gender;
So each episode has multiple people. And the join table is Episode_Person.
My UI has a datatable with a filter on each column:
The filtering already works on Event and Address. And looks like this predicate in QueryDSL:
BooleanBuilder where = new BooleanBuilder();
if (pagination.getFilterBy().getMapOfFilters().get("eventno")!=null) {
where.and(qEpisode.eventno.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("eventno")));
}
if (pagination.getFilterBy().getMapOfFilters().get("address")!=null) {
where.and(qEpisode.address.formattedAddress.containsIgnoreCase(pagination.getFilterBy().getMapOfFilters().get("address")));
}
where.and(qEpisode.creatingUser.eq(user));
List<Episode> e = episodeRepository.findAll(where);
How would I now add a 3rd predicate for case name where case name is constructed of the first two people returned in the collection of people against a episode?
UPDATE
For clarification the DTO thats backs the UI view contains the "casename" attribute. It is created in the service layer when Domain objects are converted to DTO:
episodeDashboard.setNames(episodePersonList.get(0).getPerson().getSurname().toUpperCase() +" & " +episodePersonList.get(1).getPerson().getSurname().toUpperCase());
Not easily unless you delegate some of the processing to the database.
If we can get the case_name property to be populated at the database tier rather than as a derived property in the application logic then the front-end code becomes trivial.
We can do this by means of a view. The exact definition of this will depend on your database however the output would be something like this:
episode_summary_vw
+------------+-------------------------+
| epsiode_id | case_name |
+------------+-------------------------+
| 5 | Eastwood & Joilee|
| 6 | Pitt & Aniston|
| 7 | Aniston & Pitt|
+------------+-------------------------+
For Oracle it looks like LISTAGG function is what you would want and for MySQL the GROUP_CONCAT functions. In MySQL then I think this would look something like:
CREATE VIEW episode_summary_vw as
SELECT ep.episode_id, GROUP_CONCAT(p.surname SEPARATOR ' & ')
FROM episode_person ep
INNER JOIN person p on p.id = ep.person_id
GROUP BY ep.episode_id;
-- todo: needs limit to first 2 records
Once we have a view then we can simply map the case_name to the Episode entity using the #SecondaryTable functionality of JPA:
#Entity
#Table(name = "episodes")
#SecondaryTable(name = "episode_summary_vw", primaryKeyJoinColumna = #PrimaryKeyJoinColumn(name="episode_id", reference_column_name="id"))
public class Episode {
#Column(name ="case_name", table = "episode_summary_vw")
private String caseName;
}
You then filter and sort on the property as for any other field:
if (pagination.getFilterBy().getMapOfFilters().get("caseName")!=null) {
where.and(qEpisode.caseName.containsIgnoreCase(pagination.getFilterBy().
getMapOfFilters().get("caseName")));
}

JPA Query on #OneToMany Set<Entity>

The GenericVehicle represent a vehicle which may have 0 or more GenericVehicleAccessory.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class GenericVehicle extends GenericEntity {
#Id
private Long id;
#Column(nullable=false)
#NotNull(message="{GenericVehicle.vehicleName.notNull")
#Size(max=128, message="{GenericVehicle.vehicleName.size")
private String vehicleName;
#OneToMany(mappedBy = "genericVehicle", cascade = { CascadeType.ALL }, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<GenericVehicleAccessory> accessories;
// ..
}
#Entity
#Table(name = "GENERICVEHICLEACCESSORIES")
public class GenericVehicleAccessory extends GenericEntity {
#Id
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GENERICVEHICLE_ID")
private GenericVehicle genericVehicle;
private String name;
// ..
}
I would like to perform a query which selects all GenericVehicle(s) which owns a Set (accessories) of a given criteria set.
For example:
+----+-------------------+
| ID | VEHICLENAME |
+----+-------------------+
| 1 | Toyota Auris |
| 2 | Volkswagen Passat |
| 3 | Bentley Arnage |
| 4 | Hyundai Accent |
| 5 | Toyota Auris |
+----+-------------------+
+-------------------+----------------------------------+
| GENERICVEHICLE_ID | NAME |
+-------------------+----------------------------------+
| 1 | Leather seats |
| 1 | Electronic stability control |
| 2 | Power steering |
| 4 | ABS |
| 4 | Airbag |
| 4 | Cruise control |
| 5 | Leather seats |
| 5 | Electronic stability control |
+-------------------+----------------------------------+
A criteria like this:
criteria.setVehicleName="Toyota Auris";
new GenericVehicleAccessory tmp0 = new GenericVehicleAccessory();
tmp0.setName="Leather Seats";
new GenericVehicleAccessory tmp1 = new GenericVehicleAccessory();
tmp1.setName="Electronic stability control";
criteria.addToAccessory(tmp0);
criteria.addToAccessory(tmp1);
would select the GenericVehicle entity with ID=1 (since ID=5 not have Electronic Stability Control).
How can I do that by using JPA Criteria API?

Help with HQL query (group by, order by)

I have problem writing HQL to display distinct applicationId with the latest application (newest createDate) for the following data in the table.
+---------------+-------------+----------+---------------------+
| applicationId | firstName | lastName | createDate |
+---------------+-------------+----------+---------------------+
| 1 | Mark | Johnson | 2010-05-03 00:00:00 |
| 3 | Henry | Jordan | 2010-05-03 00:00:00 |
| 5 | Cindy Spahn | Wilson | 2010-05-03 00:00:00 |
| 5 | Cindy Spahn | Wilson | 2010-05-04 00:00:00 |
| 5 | Cindy Spahn | Wilson | 2010-05-05 00:00:00 |
+---------------+-------------+----------+---------------------+
5 rows in set (0.00 sec)
Below is the result that I'm looking for:
+---------------+-------------+----------+---------------------+
| applicationId | firstName | lastName | createDate |
+---------------+-------------+----------+---------------------+
| 1 | Mark | Johnson | 2010-05-03 00:00:00 |
| 3 | Henry | Jordan | 2010-05-03 00:00:00 |
| 5 | Cindy Spahn | Wilson | 2010-05-05 00:00:00 |
+---------------+-------------+----------+---------------------+
3 rows in set (0.00 sec)
Entities are as follow:
#Entity
#Table(name = "application")
public class Application {
private long applicationId;
private String firstName;
private String lastName;
private List<ApplicationHistory> applicationHistoryList;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getApplicationId() {
return applicationId;
}
#OneToMany(mappedBy = "application", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<ApplicationHistory> getApplicationHistoryList() {
return applicationHistoryList;
}
// getter() and setter()
}
and:
#Entity
#Table(name = "applicationHistory")
public class ApplicationHistory {
private Application application;
private final Timestamp createDate = new Timestamp(System.currentTimeMillis());
#ManyToOne
#JoinColumn(name = "applicationId", insertable = false, updatable = false)
public Application getApplication() {
return application;
}
#Id
#Column(columnDefinition = "timestamp default current_timestamp")
public Timestamp getCreateDate() {
return createDate;
}
}
You can use below query:
select a, h.createDate from Application as a join a.applicationHistoryList as h where (a.applicationId, h.createDate) in( SELECT application.applicationId, max(createDate) FROM ApplicationHistory group by application.applicationId)
eg:
Query q = em.createQuery("select a, h.createDate from Application as a join a.applicationHistoryList as h where (a.applicationId, h.createDate) in( SELECT application.applicationId, max(createDate) FROM ApplicationHistory group by application.applicationId) ");
List list = q.getResultList();
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
Object obj[] = (Object[])iterator.next();
Application a = (Application) obj[0];
System.out.println("ApplicationId="+a.getApplicationId() );
System.out.println("CreateDate="+obj[1] );
}
Try to do it with group by clause:
select ah from ApplicationHistory ah group by ah.applicationId order by ah.createDate desc

Categories