Multi Level Conditional One To Many Mappings in Spring JPA - jpa

I need to have a mapping like below in Spring JPA
Teacher -> Class -> Subject
Teacher has Classes and Classes should be having its Subjects.
I am developing a School Management System.
I have the below tables
MariaDB [school-mgmt-sys]> select * from subject;
+----+---------+----------------------------+----------------------------+---------+
| id | code | date_created | last_updated | name |
+----+---------+----------------------------+----------------------------+---------+
| 1 | MATHS | 2022-07-20 14:21:20.000000 | 2022-07-20 14:21:20.000000 | Maths |
| 2 | SCIENCE | 2022-07-20 14:21:34.000000 | 2022-07-20 14:21:34.000000 | Science |
| 3 | HINDI | 2022-07-20 14:21:59.000000 | 2022-07-20 14:21:59.000000 | Hindi |
| 4 | SOCIAL | 2022-07-20 14:22:10.000000 | 2022-07-20 14:22:10.000000 | Social |
| 5 | ENGLISH | 2022-07-20 14:22:16.000000 | 2022-07-20 14:22:16.000000 | English |
+----+---------+----------------------------+----------------------------+---------+
5 rows in set (0.00 sec)
MariaDB [school-mgmt-sys]> select * from class;
+----+---------+----------------------------+----------------------------+---------+
| id | code | date_created | last_updated | name |
+----+---------+----------------------------+----------------------------+---------+
| 1 | GRADE_1 | 2022-07-20 14:23:49.000000 | 2022-07-20 14:23:49.000000 | Grade 1 |
| 2 | GRADE_2 | 2022-07-20 14:23:56.000000 | 2022-07-20 14:23:56.000000 | Grade 2 |
+----+---------+----------------------------+----------------------------+---------+
2 rows in set (0.00 sec)
MariaDB [school-mgmt-sys]> select * from teacher;
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| id | date_created | email | first_name | last_name | last_updated | phone_no |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| 1 | 2022-07-20 14:41:18.000000 | abdm#g.com | Abdul | Mannan | 2022-07-20 14:41:18.000000 | 9911223344 |
| 2 | 2022-07-20 14:41:39.000000 | anjgk#g.com | Anji | G Konda | 2022-07-20 14:41:39.000000 | 9911223355 |
| 3 | 2022-07-20 14:42:04.000000 | nvdShk#g.com | Naveed | Shaik | 2022-07-20 14:42:04.000000 | 9911223366 |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
3 rows in set (0.00 sec)
I want to create a mapping like below
Teacher -> Set<Class> -> Set<Subject>
Teacher will be teaching multiple classes. And In each class he will be teaching different subjects.
Ex:
Teacher1 - Class1 -> Sub1, Sub2
- Class2 -> Sub1, Sub4
So I want to have a mapping when I fetch a teacher, it has to have all classes with respective subject he teachers. Output expected is
teacher {
"name" : "Abdul",
"classes" [
{
"id": "Class1",
"name": "Class 1"
"subjects": [
{
"id" : "SUB1",
"name" "MAths"
},
{
"id" : "SUB2",
"name" "HIndi"
}
]
},
{
"id": "Class2",
"name": "Class 2",
"subjects": [
{
"id" : "SUB1",
"name" "MAths"
},
{
"id" : "SUB4",
"name" "English"
}
]
}
]
}
I tried below.
#Entity
#Table(name = "teacher")
public class SMSTeacher {
...
#OneToMany(mappedBy = "teacher")
private Set<SMSClass> classes;
}
#Entity
#Table(name = "class")
public class SMSClass {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "smsClass")
private Set<SMSSubject> subjects;
#ManyToOne
private SMSTeacher teacher;
}
#Entity
#Table(name = "subject")
public class SMSSubject {
...
#ManyToOne
private SMSClass smsClass;
}
It has created below table structure and I inserted data
MariaDB [school-mgmt-sys]> select * from teacher;
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| id | date_created | email | first_name | last_name | last_updated | phone_no |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
| 1 | 2022-07-20 17:40:57.000000 | nvdshk#g.com | Navid | Shaik | 2022-07-20 17:40:57.000000 | 9911223366 |
| 2 | 2022-07-20 17:41:21.000000 | abdMn#g.com | Abdul | Mannna | 2022-07-20 17:41:21.000000 | 9911223355 |
| 4 | 2022-07-20 17:41:50.000000 | anjgkn#g.com | Anji | GK | 2022-07-20 17:41:50.000000 | 9911223377 |
+----+----------------------------+--------------+------------+-----------+----------------------------+------------+
3 rows in set (0.01 sec)
MariaDB [school-mgmt-sys]> select * from class;
+----+---------+----------------------------+----------------------------+---------+------------+
| id | code | date_created | last_updated | name | teacher_id |
+----+---------+----------------------------+----------------------------+---------+------------+
| 1 | GRADE_1 | 2022-07-20 17:42:46.000000 | 2022-07-20 17:42:46.000000 | Grade 1 | 1 |
| 2 | GRADE_1 | 2022-07-20 17:43:28.000000 | 2022-07-20 17:43:28.000000 | Grade 1 | 2 |
| 3 | GRADE_2 | 2022-07-20 17:44:20.000000 | 2022-07-20 17:44:20.000000 | Grade 2 | 2 |
| 4 | GRADE_2 | 2022-07-20 17:49:21.000000 | 2022-07-20 17:49:21.000000 | Grade 2 | 2 |
+----+---------+----------------------------+----------------------------+---------+------------+
4 rows in set (0.01 sec)
MariaDB [school-mgmt-sys]> select * from subject;
+----+------+----------------------------+----------------------------+---------+--------------+
| id | code | date_created | last_updated | name | sms_class_id |
+----+------+----------------------------+----------------------------+---------+--------------+
| 1 | ENG | 2022-07-20 17:46:32.000000 | 2022-07-20 17:46:32.000000 | English | 3 |
| 2 | HIN | 2022-07-20 17:46:40.000000 | 2022-07-20 17:46:40.000000 | Hindi | 4 |
| 3 | MATH | 2022-07-20 17:46:52.000000 | 2022-07-20 17:46:52.000000 | Maths | 2 |
| 4 | MATH | 2022-07-20 17:46:59.000000 | 2022-07-20 17:46:59.000000 | Maths | 1 |
+----+------+----------------------------+----------------------------+---------+--------------+
4 rows in set (0.01 sec)
to fetch the above data, am using Spring Data Rest API when I fetch teacher it will have a link to navigate to class and from there i can go to subject.
But not able to do what am looking for.
Please help.

I tried a couple of solutions. Finally I found the below solution.
I have created one to many mappings between the entities with join tables, to maintain unique data in tables.
Entities:
#Entity
#Table(name = "teacher")
public class SMSTeacher {
...
#OneToMany
#JoinTable(name = "teacher_class", joinColumns = #JoinColumn(name = "teacher_id"), inverseJoinColumns = #JoinColumn(name = "class_id"))
private Set<SMSClass> classes = new HashSet<>();
}
#Entity
#Table(name = "class")
public class SMSClass {
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "class_subject", joinColumns = #JoinColumn(name = "class_id"), inverseJoinColumns = #JoinColumn(name = "subject_id"))
private Set<SMSSubject> subjects = new HashSet<>();;
}
#Entity
#Table(name = "subject")
public class SMSSubject {
...
//No mapping here, Configured only uni directional mapping.
}
A third table to maintain teacher-> class -> subject relation ship data.
+----+----------+------------+------------+
| id | class_id | subject_id | teacher_id |
+----+----------+------------+------------+
| 1 | 1 | 3 | 1 |
| 2 | 1 | 5 | 1 |
| 3 | 2 | 2 | 1 |
| 4 | 2 | 4 | 1 |
| 5 | 1 | 3 | 2 |
| 6 | 2 | 3 | 3 |
+----+----------+------------+------------+
Created a JPA native SQL to fetch the result as required.
public interface TeacherClassSubjectRepo extends JpaRepository<TeacherClassSubject, Long> {
#Query(value = "select t.id T_ID, t.first_name fName, t.last_name lName, t.phone_no phone, t.email email, c.id C_ID, c.name C_NAME, c.code C_CODE, s.id S_ID, s.name S_NAME, s.code S_CODE from teacher t, class c,subject s, teacher_class_subject tcs where tcs.teacher_id=t.id and tcs.class_id = c.id and tcs.subject_id = s.id and t.id=?1 order by t.id, c.id", nativeQuery = true)
List<Tuple> findAllByTeacherId(Long teacherId);
}
Here is my service Impl method to generate the output as required.
#Override
public ResponseEntity<Object> getTeacher(Long teacherId) {
List<Tuple> teacherClassSubjects = teacherClassSubjectRepo
.findAllByTeacherId(teacherId);
if (teacherClassSubjects.size() == 0)
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
SMSTeacherDTO smsTeacherDTO = null;
String fName = "", lName = "", email = "", currCName = "", currCCode = "";
BigInteger phone = null;
Set<SMSSubjectDTO> subjectDTOs = new HashSet<>();
Set<SMSClassDTO> classDTOs = new HashSet<>();
for (Tuple tuple : teacherClassSubjects) {
...
//Logic to fetch SMSTeacherDTO fields and Logic to construct classDTOs
}
smsTeacherDTO = new SMSTeacherDTO(fName, lName, email, phone, classDTOs);
return new ResponseEntity<>(smsTeacherDTO, HttpStatus.OK);
}
Output:
{
"firstName": "Anji",
"lastName": "GK",
"email": "anjgkn#g.com",
"phoneNo": 9911223311,
"classes": [
{
"name": "Grade 2",
"code": "GRADE_2",
"subjects": [
{
"name": "Science",
"code": "SCIENCE"
},
{
"name": "Social",
"code": "SOCIAL"
}
]
},
{
"name": "Grade 1",
"code": "GRADE_1",
"subjects": [
{
"name": "Hindi",
"code": "HINDI"
},
{
"name": "English",
"code": "ENGLISH"
}
]
}
]
}
Please comment if this solution is good or if there is any better solution available for this problem.
Thanks.

Related

PostgreSQL - Calculate SUM() of COUNT()

Basically I have a table called cities which looks like this:
+------+-----------+---------+----------+----------------+
| id | name | lat | lng | submitted_by |
|------+-----------+---------+----------+----------------|
| 1 | Pyongyang | 39.0392 | 125.7625 | 15 |
| 2 | Oslo | 59.9139 | 10.7522 | 8 |
| 3 | Hebron | 31.5326 | 35.0998 | 8 |
| 4 | Hebron | 31.5326 | 35.0998 | 10 |
| 5 | Paris | 48.8566 | 2.3522 | 12 |
| 6 | Hebron | 31.5326 | 35.0998 | 7 |
+------+-----------+---------+----------+----------------+
Desired result:
+-----------+---------+
| name | count |
|-----------+---------|
| Hebron | 3 |
| Pyongyang | 1 |
| Oslo | 1 |
| Paris | 1 |
| Total | 6 | <-- The tricky part
+-----------+---------+
In other words, what I need to do is SELECT the SUM of the COUNT in the query I'm currently using:
SELECT name, count(name)::int FROM cities GROUP BY name;
But apparently nested aggregated functions are not allowed in PostgreSQL. I'm guessing I need to use ROLLUP in some way but I can't seem to get it right.
Thanks for the help.
You need to UNION ALL the total sum.
WITH ROLLUP works by summing up the total for every group separate and can't be used here.
CREATE TABLE cities (
"id" INTEGER,
"name" VARCHAR(9),
"lat" FLOAT,
"lng" FLOAT,
"submitted_by" INTEGER
);
INSERT INTO cities
("id", "name", "lat", "lng", "submitted_by")
VALUES
('1', 'Pyongyang', '39.0392', '125.7625', '15'),
('2', 'Oslo', '59.9139', '10.7522', '8'),
('3', 'Hebron', '31.5326', '35.0998', '8'),
('4', 'Hebron', '31.5326', '35.0998', '10'),
('5', 'Paris', '48.8566', '2.3522', '12'),
('6', 'Hebron', '31.5326', '35.0998', '7');
SELECT name, COUNT(name)::int FROM cities GROUP BY name
UNION ALL
SELECT 'Total', COUNT(*) FROM cities
name | count
:-------- | ----:
Hebron | 3
Pyongyang | 1
Oslo | 1
Paris | 1
Total | 6
db<>fiddle here

How do I join tables while putting the results in a json array?

Table name: people
+----+------+-------------+-------+
| id | name | city | state |
+----+------+-------------+-------+
| 1 | Joe | Los Angeles | CA |
+----+------+-------------+-------+
| 2 | Jill | Miami | FL |
+----+------+-------------+-------+
| 3 | Asa | Portland | OR |
+----+------+-------------+-------+
Table name: pets
+----+----------+------+
| id | pet_name | type |
+----+----------+------+
| 1 | Spike | dog |
+----+----------+------+
| 1 | Fluffy | cat |
+----+----------+------+
| 2 | Oscar | dog |
+----+----------+------+
How would I join the two tables above to include a column containing JSON of results matched in the 'pets' table (PostgreSQL)?
+----+------+------------------------------------------------------------+
| id | name | pets |
+----+------+------------------------------------------------------------+
| 1 | Joe | [{name:'Spike', type:'dog'}, {name: 'Fluffy', type:'cat'}] |
+----+------+------------------------------------------------------------+
| 2 | Jill | [{name:'Oscar', type:'dog'}] |
+----+------+------------------------------------------------------------+
| 3 | Asa | [] |
+----+------+------------------------------------------------------------+
Use json_agg() to aggregate over json-objects:
SELECT people.id
, name
, json_agg(
CASE WHEN pet_name IS NOT NULL THEN
json_build_object(
'name', pet_name
, 'type', type
)
END
)
FROM people
LEFT JOIN pets ON people.id = pets.id
GROUP BY
people.id
, name
ORDER BY
people.id;

Select common values when using group by [Postgres]

I have three main tables meetings, persons, hobbies with two relational tables.
Table meetings
+---------------+
| id | subject |
+----+----------+
| 1 | Kickoff |
| 2 | Relaunch |
| 3 | Party |
+----+----------+
Table persons
+------------+
| id | name |
+----+-------+
| 1 | John |
| 2 | Anna |
| 3 | Linda |
+----+-------+
Table hobbies
+---------------+
| id | name |
+----+----------+
| 1 | Soccer |
| 2 | Tennis |
| 3 | Swimming |
+----+----------+
Relation Table meeting_person
+-----------------+-----------+
| id | meeting_id | person_id |
+----+------------+-----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+------------+-----------+
Relation Table person_hobby
+----------------+----------+
| id | person_id | hobby_id |
+----+-----------+----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
+----+-----------+----------+
Now I want to to find the common hobbies of all person attending each meeting.
So the desired result would be:
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
| | (Aggregated) | (Aggregated) |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer |
| 2 | John,Anna | Soccer,Tennis |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
My current work in progress is:
select
m.id as "meeting_id",
(
select string_agg(distinct p.name, ',')
from meeting_person mp
inner join persons p on mp.person_id = p.id
where m.id = mp.meeting_id
) as "persons",
string_agg(distinct h2.name , ',') as "common_hobbies"
from meetings m
inner join meeting_person mp2 on m.id = mp2.meeting_id
inner join persons p2 on mp2.person_id = p2.id
inner join person_hobby ph2 on p2.id = ph2.person_id
inner join hobbies h2 on ph2.hobby_id = h2.id
group by m.id
But this query lists not the common_hobbies but all hobbies which are at least once mentioned.
+------------+-----------------+------------------------+
| meeting_id | persons | common_hobbies |
+------------+-----------------+------------------------+
| 1 | John,Anna,Linda | Soccer,Tennis,Swimming |
| 2 | John,Anna | Soccer,Tennis,Swimming |
| 3 | John | Soccer,Tennis,Swimming |
+------------+-----------------+------------------------+
Does anyone have any hints for me, on how I could solve this problem?
Cheers
This problem can be solved by implement custom aggregation function (found it here):
create or replace function array_intersect(anyarray, anyarray)
returns anyarray language sql
as $$
select
case
when $1 is null then $2
when $2 is null then $1
else
array(
select unnest($1)
intersect
select unnest($2))
end;
$$;
create aggregate array_intersect_agg (anyarray)
(
sfunc = array_intersect,
stype = anyarray
);
So, the solution can be next:
select
meeting_id,
array_agg(ph.name) persons,
array_intersect_agg(hobby) common_hobbies
from meeting_person mp
join (
select p.id, p.name, array_agg(h.name) hobby
from person_hobby ph
join persons p on ph.person_id = p.id
join hobbies h on h.id = ph.hobby_id
group by p.id, p.name
) ph on ph.id = mp.person_id
group by meeting_id;
Look the example fiddle
Result:
meeting_id | persons | common_hobbies
-----------+-----------------------+--------------------------
1 | {John,Anna,Linda} | {Soccer}
3 | {John} | {Soccer,Tennis,Swimming}
2 | {John,Anna} | {Soccer,Tennis}

Grouping data of multiple rows into groups according to an id in codeigniter

I have a table like this in database
+---+-------------+--------------+
|id | service_name| doc_id |org_id|
+---+-------------+--------------+
| 1 | new service | 12 | 119 |
| | | | |
| 2 | new service | 24 | 119 |
| | | | |
| 3 | old service | 13 | 118 |
| | | | |
| 4 | old service | 14 | 118 |
| | | | |
| 5 | new service | 20 | 119 |
+---+-------------+--------------+
I want to group all the doc_id's according to service_name column
I have tried using
IN my controller
$where_person['org_id'] = $this->post('org_id');
$result_insert = $this->$model_name->fetch_doctor_services($where_person);
In my Model
function fetch_doctor_services($where){
$this->db->select('service_name,doc_id')->from('services');
$this->db->group_by('service_name');
$this->db->where($where);
return $this->db->get()->result();
}
But it does not output data as i desire, by grouping by service_name and all the doc_id's according to that service_name.
where am i going wrong here?
Currently my output is like this.
{ "data":
[ { "service_name": "new service", "doc_id": "12" },
{
"service_name": "old service", "doc_id": "13" }
]
}
You need to use GROUP_CONCAT. See below code on how to use it
$this->db->select('service_name, GROUP_CONCAT( doc_id) ')->from('services');
$this->db->group_by('service_name');
$this->db->where($where);
return $this->db->get()->result();

How to create a calculated field which uses multiple row data in tableau?

I have this kind of data :
-----------------------------------------------------
| ID | City | District | Amount |
-----------------------------------------------------
| 121 | Delhi | D1 | 1000$ |
| 122 | Delhi | D2 | 1200$ |
| 123 | Moscow | M1 | 1125$ |
| 124 | Delhi | D3 | 7600$ |
| 125 | Shanghai | S1 | 3400$ |
| 126 | Shanghai | S2 | 3100$ |
| 127 | Moscow | M2 | 9900$ |
-----------------------------------------------------
I want to create a table calculation which basically check if some District are in some City.
// Calculated field
IF [city] = 'Delhi'
AND [District] = 'D1'
AND [District] = 'D2'
THEN "Category 1"
ELSE IF [city] = 'Moscow'
AND [District] = 'M1'
AND [District] = 'M3'
THEN "Category 2"
END
I know this calculated field is wrong. I am not able to find a workaround to create the same logic as present in the (wrong) calculated field here. What is the right way to create this calculated field ?