Как написать JPQL-запрос для выбора данных из двух таблиц, который возвращает полный набор записей из первой - PullRequest
0 голосов
/ 22 мая 2019

Я имею дело с приложением Spring-Boot с JPA на борту и базой данных в Postgres.

Текущая цель - извлечь записи из одной таблицы вместе с некоторой агрегированной статистикой из другой.

В традиционном SQL я бы использовал оператор LEFT JOIN (подзапрос) , например:

SELECT d.*, stats.avg_salary
FROM departments d
LEFT JOIN
  (SELECT e.dep_id, avg(e.salary) AS avg_salary
  FROM employees e
  GROUP BY e.dep_id) stats
ON (d.id = stats.dep_id)

А теперь мне нужно нечто подобное в JPQL.

Смысл состоит в том, чтобы извлечь все отделы, включая те, в которых есть работники с нулевой зарплатой или вообще нет работников.

Запрос, который я создал до сих пор (см. Ниже), возвращает только те подразделения, у которых есть совпадение (он работает как INNER JOIN ), но мне нужно включить также записи со средним окладом NULL.

@Query("SELECT d, avg(e.salary) "
  +"FROM Department d, Employee e "
  +"WHERE (e.department = d) "
  +"GROUP BY d")
public List<Tuple> getDepartmentsStats();

Какой самый элегантный способ написать такой запрос в JPQL?

Решение, которое я выбрал

Когда мои запросы стали немного сложнее, я понял, что наиболее подходящий способ - это использовать собственные запросы и проекции, как предложено @ Dirk Deyne .

Я бы сказал, что это делает классы сущностей и модель данных ясными и простыми, в то же время дает свободу для создания сложных запросов.

1 Ответ

0 голосов
/ 22 мая 2019

вы можете использовать собственный запрос, например

  @Query(value =
    "SELECT d.name, stats.avg_salary " + 
    "FROM department d " + 
    "LEFT JOIN " + 
    "  (SELECT e.department_id, avg(e.salary) AS avg_salary " + 
    "  FROM employee e " + 
    "  GROUP BY e.department_id) stats " + 
    "ON (d.id = stats.department_id)", nativeQuery = true
    )
  public List<Tuple> getNativeDepartmentsStats();

, и если Department имеет тесную связь с Employee, как

@Entity
class Department {
  @Id @GeneratedValue
  Long id;
  String name;
  @OneToMany(fetch = FetchType.EAGER, mappedBy = "department")
  Set<Employee> employees;
...

, то вы можете использовать чистый JPQL, как

  @Query("SELECT d , avg(e.salary) FROM Department d left join d.employees e GROUP BY d")
  public List<Tuple> getFullDepartmentsStats();

Возможно, лучшим решением будет использовать проекцию типа

interface DepAvg {
  Department getDepartment();
  Long getAverage();
}

, тогда вы можете использовать что-то вроде

  @Query(value =
      "SELECT dep.name Department, stats.Average Average " + 
      "FROM department dep " + 
      "LEFT JOIN " + 
      "  (SELECT e.department_id, avg(e.salary) AS Average " + 
      "  FROM employee e " + 
      "  GROUP BY e.department_id) stats " + 
      "ON (dep.id = stats.department_id)", nativeQuery = true
      )
    public List<DepAvg> getDepartmentsAvg();
...