Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit 3239c22

Browse files
Optimized contact, case, deathcount queries SORMAS-Foundation#3276 (SORMAS-Foundation#3981)
* Merge subqueries into single lightweight query * disable query logging SORMAS-Foundation#3276 * Add a preliminary way to calculate contact counts via native queries SORMAS-Foundation#3276 * Move contact query to criteriaEngine query * Update export queries. Index to specific contact and eventparticipant fields. SORMAS-Foundation#3276 * Update schema_version correctly SORMAS-Foundation#3276 * Add contact count selector to EventGrid SORMAS-Foundation#3276 * Add i18n captions SORMAS-Foundation#3276 * rename criteria queries SORMAS-Foundation#3276 * Fix Captions.java order SORMAS-Foundation#3276 * Implement change requests SORMAS-Foundation#3276 * Fix missing change in EventGrid.java SORMAS-Foundation#3276 * Rename EventContactCountMethod enum values and hide related columns correctly SORMAS-Foundation#3276 * Fix detailed export caption SORMAS-Foundation#3276 * Add option to show both contact count methods at once SORMAS-Foundation#3276
1 parent 2599f0a commit 3239c22

10 files changed

Lines changed: 239 additions & 85 deletions

File tree

sormas-api/src/main/java/de/symeda/sormas/api/event/EventExportDto.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class EventExportDto implements Serializable {
3535
private long participantCount;
3636
private long caseCount;
3737
private long deathCount;
38+
private long contactCount;
39+
private long contactCountSourceInEvent;
3840
private Disease disease;
3941
private String diseaseDetails;
4042
private Date startDate;
@@ -429,13 +431,31 @@ public long getDeathCount() {
429431
return deathCount;
430432
}
431433

434+
public void setDeathCount(long deathCount) {
435+
this.deathCount = deathCount;
436+
}
437+
432438
@Order(37)
439+
public long getContactCount() {
440+
return contactCount;
441+
}
442+
443+
public void setContactCount(long contactCount) {
444+
this.contactCount = contactCount;
445+
}
446+
447+
@Order(38)
448+
public long getContactCountSourceInEvent() {
449+
return contactCountSourceInEvent;
450+
}
451+
452+
@Order(39)
433453
public String getExternalToken() {
434454
return externalToken;
435455
}
436456

437-
public void setDeathCount(long deathCount) {
438-
this.deathCount = deathCount;
457+
public void setContactCountSourceInEvent(long contactCountSourceInEvent) {
458+
this.contactCountSourceInEvent = contactCountSourceInEvent;
439459
}
440460

441461
public EventJurisdictionDto getJurisdiction() {

sormas-api/src/main/java/de/symeda/sormas/api/event/EventIndexDto.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class EventIndexDto extends PseudonymizableIndexDto implements Serializab
3737
public static final String PARTICIPANT_COUNT = "participantCount";
3838
public static final String CASE_COUNT = "caseCount";
3939
public static final String DEATH_COUNT = "deathCount";
40+
public static final String CONTACT_COUNT = "contactCount";
41+
public static final String CONTACT_COUNT_SOURCE_IN_EVENT = "contactCountSourceInEvent";
4042
public static final String DISEASE = "disease";
4143
public static final String DISEASE_DETAILS = "diseaseDetails";
4244
public static final String START_DATE = "startDate";
@@ -61,6 +63,14 @@ public class EventIndexDto extends PseudonymizableIndexDto implements Serializab
6163
private long participantCount;
6264
private long caseCount;
6365
private long deathCount;
66+
/**
67+
* number of contacts whose person is involved in this event
68+
*/
69+
private long contactCount;
70+
/**
71+
* number of contacts whose person is involved in this event, and where the source case is also part of the event
72+
*/
73+
private long contactCountSourceInEvent;
6474
private Disease disease;
6575
private String diseaseDetails;
6676
private Date startDate;
@@ -293,6 +303,22 @@ public void setDeathCount(long deathCount) {
293303
this.deathCount = deathCount;
294304
}
295305

306+
public long getContactCount() {
307+
return contactCount;
308+
}
309+
310+
public void setContactCount(long contactCount) {
311+
this.contactCount = contactCount;
312+
}
313+
314+
public long getContactCountSourceInEvent() {
315+
return contactCountSourceInEvent;
316+
}
317+
318+
public void setContactCountSourceInEvent(long contactCountSourceInEvent) {
319+
this.contactCountSourceInEvent = contactCountSourceInEvent;
320+
}
321+
296322
public String getRegion() {
297323
return getEventLocation().getRegion();
298324
}

sormas-api/src/main/java/de/symeda/sormas/api/i18n/Captions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,9 @@ public interface Captions {
785785
String Event = "Event";
786786
String Event_caseCount = "Event.caseCount";
787787
String Event_connectionNumber = "Event.connectionNumber";
788+
String Event_contactCount = "Event.contactCount";
789+
String Event_contactCountMethod = "Event.contactCountMethod";
790+
String Event_contactCountSourceInEvent = "Event.contactCountSourceInEvent";
788791
String Event_deathCount = "Event.deathCount";
789792
String Event_diseaseDetails = "Event.diseaseDetails";
790793
String Event_diseaseShort = "Event.diseaseShort";

sormas-api/src/main/resources/captions.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,9 @@ eventLinkToEventsWithinTheSameFacility=See events within the same facility
893893

894894
Event=Event
895895
Event.caseCount=Cases
896+
Event.contactCount=Contacts
897+
Event.contactCountMethod=Contact Count Method
898+
Event.contactCountSourceInEvent=Contacts with source case in event
896899
Event.deathCount=Fatalities
897900
Event.diseaseDetails=Disease name
898901
Event.diseaseShort=Disease

sormas-api/src/main/resources/enum.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,11 @@ EpiWeekFilterOption.LAST_WEEK = Last Week
443443
EpiWeekFilterOption.SPECIFY_WEEK = Specify
444444
EpiWeekFilterOption.THIS_WEEK = This Week
445445

446+
# EventContactCountMethod
447+
EventContactCountMethod.ALL = Count all contacts
448+
EventContactCountMethod.SOURCE_CASE_IN_EVENT = Only count contacts with source case in event
449+
EventContactCountMethod.BOTH_METHODS = Show both methods
450+
446451
# EventInvestigationStatus
447452
EventInvestigationStatus.DISCARDED=Investigation discarded
448453
EventInvestigationStatus.DONE=Investigation done

sormas-backend/src/main/java/de/symeda/sormas/backend/event/EventFacadeEjb.java

Lines changed: 112 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -334,57 +334,74 @@ public List<EventIndexDto> getIndexList(EventCriteria eventCriteria, Integer fir
334334
}
335335

336336
Map<String, Long> participantCounts = new HashMap<>();
337-
Map<String, Long> caseCounts = new HashMap<>();;
337+
Map<String, Long> caseCounts = new HashMap<>();
338338
Map<String, Long> deathCounts = new HashMap<>();
339+
Map<String, Long> contactCounts = new HashMap<>();
340+
Map<String, Long> contactCountsSourceInEvent = new HashMap<>();
339341
if (indexList != null) {
340342
List<Object[]> objectQueryList = null;
341343

342-
CriteriaQuery<Object[]> objectCQ = cb.createQuery(Object[].class);
343-
Root<Event> eventRoot = objectCQ.from(Event.class);
344-
345-
// number of Participants
346-
Subquery<Long> participantCount = objectCQ.subquery(Long.class);
347-
Root<EventParticipant> eventParticipantRoot = participantCount.from(EventParticipant.class);
348-
Predicate assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
349-
Predicate notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
350-
participantCount.select(cb.count(eventParticipantRoot));
351-
participantCount.where(assignedToEvent, notDeleted);
352-
353-
// number of cases among event participants
354-
Subquery<Long> caseCount = objectCQ.subquery(Long.class);
355-
eventParticipantRoot = caseCount.from(EventParticipant.class);
356-
assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
357-
notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
358-
Predicate isCase = cb.isNotNull(eventParticipantRoot.get(EventParticipant.RESULTING_CASE));
359-
caseCount.select(cb.count(eventParticipantRoot));
360-
caseCount.where(assignedToEvent, notDeleted, isCase);
361-
362-
// number of fatalities among event participant cases
363-
Subquery<Long> deathsCount = objectCQ.subquery(Long.class);
364-
eventParticipantRoot = deathsCount.from(EventParticipant.class);
365-
Join<EventParticipant, Case> caseJoin = eventParticipantRoot.join(EventParticipant.RESULTING_CASE, JoinType.LEFT);
366-
assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
367-
notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
368-
isCase = cb.isNotNull(eventParticipantRoot.get(EventParticipant.RESULTING_CASE));
369-
Predicate isDead = cb.equal(caseJoin.get(Case.OUTCOME), CaseOutcome.DECEASED);
370-
deathsCount.select(cb.count(eventParticipantRoot));
371-
deathsCount.where(assignedToEvent, notDeleted, isCase, isDead);
372-
373-
objectCQ.multiselect(eventRoot.get(Event.UUID), participantCount, caseCount, deathsCount);
374-
objectQueryList = em.createQuery(objectCQ).getResultList();
375-
objectQueryList.forEach(r -> {
376-
participantCounts.put((String) r[0], (Long) r[1]);
377-
caseCounts.put((String) r[0], (Long) r[2]);
378-
deathCounts.put((String) r[0], (Long) r[3]);
379-
});
344+
// Participant, Case and Death Count
345+
CriteriaQuery<Object[]> participantCQ = cb.createQuery(Object[].class);
346+
Root<EventParticipant> epRoot = participantCQ.from(EventParticipant.class);
347+
Join<EventParticipant, Case> caseJoin = epRoot.join(EventParticipant.RESULTING_CASE, JoinType.LEFT);
348+
Predicate notDeleted = cb.isFalse(epRoot.get(EventParticipant.DELETED));
349+
participantCQ.multiselect(
350+
epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID),
351+
cb.count(epRoot),
352+
cb.sum(cb.selectCase().when(cb.isNotNull(epRoot.get(EventParticipant.RESULTING_CASE)), 1).otherwise(0).as(Long.class)),
353+
cb.sum(cb.selectCase().when(cb.equal(caseJoin.get(Case.OUTCOME), CaseOutcome.DECEASED), 1).otherwise(0).as(Long.class)));
354+
participantCQ.where(notDeleted);
355+
participantCQ.groupBy(epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID));
356+
357+
objectQueryList = em.createQuery(participantCQ).getResultList();
358+
359+
if (objectQueryList != null) {
360+
objectQueryList.forEach(r -> {
361+
participantCounts.put((String) r[0], (Long) r[1]);
362+
caseCounts.put((String) r[0], (Long) r[2]);
363+
deathCounts.put((String) r[0], (Long) r[3]);
364+
});
365+
}
380366

367+
// Contact Count (with and without sourcecase in event) using theta join
368+
CriteriaQuery<Object[]> contactCQ = cb.createQuery(Object[].class);
369+
epRoot = contactCQ.from(EventParticipant.class);
370+
Root<Contact> contactRoot = contactCQ.from(Contact.class);
371+
Predicate participantPersonEqualsContactPerson = cb.equal(epRoot.get(EventParticipant.PERSON), contactRoot.get(Contact.PERSON));
372+
notDeleted = cb.isFalse(epRoot.get(EventParticipant.DELETED));
373+
Predicate contactNotDeleted = cb.isFalse(contactRoot.get(Contact.DELETED));
374+
375+
Subquery<EventParticipant> sourceCaseSubquery = contactCQ.subquery(EventParticipant.class);
376+
Root<EventParticipant> epr2 = sourceCaseSubquery.from(EventParticipant.class);
377+
sourceCaseSubquery.select(epr2);
378+
sourceCaseSubquery.where(
379+
cb.equal(epr2.get(EventParticipant.RESULTING_CASE), contactRoot.get(Contact.CAZE)),
380+
cb.equal(epr2.get(EventParticipant.EVENT), epRoot.get(EventParticipant.EVENT)));
381+
382+
contactCQ.multiselect(
383+
epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID),
384+
cb.count(epRoot),
385+
cb.sum(cb.selectCase().when(cb.exists(sourceCaseSubquery), 1).otherwise(0).as(Long.class)));
386+
contactCQ.where(participantPersonEqualsContactPerson, notDeleted, contactNotDeleted);
387+
contactCQ.groupBy(epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID));
388+
389+
objectQueryList = em.createQuery(contactCQ).getResultList();
390+
if (objectQueryList != null) {
391+
objectQueryList.forEach(r -> {
392+
contactCounts.put((String) r[0], ((Long) r[1]));
393+
contactCountsSourceInEvent.put((String) r[0], ((Long) r[2]));
394+
});
395+
}
381396
}
382397

383398
if (indexList != null) {
384399
for (EventIndexDto eventDto : indexList) {
385400
Optional.ofNullable(participantCounts.get(eventDto.getUuid())).ifPresent(eventDto::setParticipantCount);
386401
Optional.ofNullable(caseCounts.get(eventDto.getUuid())).ifPresent(eventDto::setCaseCount);
387402
Optional.ofNullable(deathCounts.get(eventDto.getUuid())).ifPresent(eventDto::setDeathCount);
403+
Optional.ofNullable(contactCounts.get(eventDto.getUuid())).ifPresent(eventDto::setContactCount);
404+
Optional.ofNullable(contactCountsSourceInEvent.get(eventDto.getUuid())).ifPresent(eventDto::setContactCountSourceInEvent);
388405
}
389406
}
390407

@@ -465,57 +482,74 @@ public List<EventExportDto> getExportList(EventCriteria eventCriteria, Integer f
465482
}
466483

467484
Map<String, Long> participantCounts = new HashMap<>();
468-
Map<String, Long> caseCounts = new HashMap<>();;
485+
Map<String, Long> caseCounts = new HashMap<>();
469486
Map<String, Long> deathCounts = new HashMap<>();
487+
Map<String, Long> contactCounts = new HashMap<>();
488+
Map<String, Long> contactCountsSourceInEvent = new HashMap<>();
470489
if (exportList != null) {
471490
List<Object[]> objectQueryList = null;
472491

473-
CriteriaQuery<Object[]> objectCQ = cb.createQuery(Object[].class);
474-
Root<Event> eventRoot = objectCQ.from(Event.class);
475-
476-
// number of Participants
477-
Subquery<Long> participantCount = objectCQ.subquery(Long.class);
478-
Root<EventParticipant> eventParticipantRoot = participantCount.from(EventParticipant.class);
479-
Predicate assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
480-
Predicate notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
481-
participantCount.select(cb.count(eventParticipantRoot));
482-
participantCount.where(assignedToEvent, notDeleted);
483-
484-
// number of cases among event participants
485-
Subquery<Long> caseCount = objectCQ.subquery(Long.class);
486-
eventParticipantRoot = caseCount.from(EventParticipant.class);
487-
assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
488-
notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
489-
Predicate isCase = cb.isNotNull(eventParticipantRoot.get(EventParticipant.RESULTING_CASE));
490-
caseCount.select(cb.count(eventParticipantRoot));
491-
caseCount.where(assignedToEvent, notDeleted, isCase);
492-
493-
// number of fatalities among event participant cases
494-
Subquery<Long> deathsCount = objectCQ.subquery(Long.class);
495-
eventParticipantRoot = deathsCount.from(EventParticipant.class);
496-
Join<EventParticipant, Case> caseJoin = eventParticipantRoot.join(EventParticipant.RESULTING_CASE, JoinType.LEFT);
497-
assignedToEvent = cb.equal(eventParticipantRoot.get(EventParticipant.EVENT), eventRoot.get(AbstractDomainObject.ID));
498-
notDeleted = cb.isFalse(eventParticipantRoot.get(EventParticipant.DELETED));
499-
isCase = cb.isNotNull(eventParticipantRoot.get(EventParticipant.RESULTING_CASE));
500-
Predicate isDead = cb.equal(caseJoin.get(Case.OUTCOME), CaseOutcome.DECEASED);
501-
deathsCount.select(cb.count(eventParticipantRoot));
502-
deathsCount.where(assignedToEvent, notDeleted, isCase, isDead);
503-
504-
objectCQ.multiselect(eventRoot.get(Event.UUID), participantCount, caseCount, deathsCount);
505-
objectQueryList = em.createQuery(objectCQ).getResultList();
506-
objectQueryList.forEach(r -> {
507-
participantCounts.put((String) r[0], (Long) r[1]);
508-
caseCounts.put((String) r[0], (Long) r[2]);
509-
deathCounts.put((String) r[0], (Long) r[3]);
510-
});
492+
// Participant, Case and Death Count
493+
CriteriaQuery<Object[]> participantCQ = cb.createQuery(Object[].class);
494+
Root<EventParticipant> epRoot = participantCQ.from(EventParticipant.class);
495+
Join<EventParticipant, Case> caseJoin = epRoot.join(EventParticipant.RESULTING_CASE, JoinType.LEFT);
496+
Predicate notDeleted = cb.isFalse(epRoot.get(EventParticipant.DELETED));
497+
participantCQ.multiselect(
498+
epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID),
499+
cb.count(epRoot),
500+
cb.sum(cb.selectCase().when(cb.isNotNull(epRoot.get(EventParticipant.RESULTING_CASE)), 1).otherwise(0).as(Long.class)),
501+
cb.sum(cb.selectCase().when(cb.equal(caseJoin.get(Case.OUTCOME), CaseOutcome.DECEASED), 1).otherwise(0).as(Long.class)));
502+
participantCQ.where(notDeleted);
503+
participantCQ.groupBy(epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID));
504+
505+
objectQueryList = em.createQuery(participantCQ).getResultList();
506+
507+
if (objectQueryList != null) {
508+
objectQueryList.forEach(r -> {
509+
participantCounts.put((String) r[0], (Long) r[1]);
510+
caseCounts.put((String) r[0], (Long) r[2]);
511+
deathCounts.put((String) r[0], (Long) r[3]);
512+
});
513+
}
511514

515+
// Contact Count (with and without sourcecase in event) using theta join
516+
CriteriaQuery<Object[]> contactCQ = cb.createQuery(Object[].class);
517+
epRoot = contactCQ.from(EventParticipant.class);
518+
Root<Contact> contactRoot = contactCQ.from(Contact.class);
519+
Predicate participantPersonEqualsContactPerson = cb.equal(epRoot.get(EventParticipant.PERSON), contactRoot.get(Contact.PERSON));
520+
notDeleted = cb.isFalse(epRoot.get(EventParticipant.DELETED));
521+
Predicate contactNotDeleted = cb.isFalse(contactRoot.get(Contact.DELETED));
522+
523+
Subquery<EventParticipant> sourceCaseSubquery = contactCQ.subquery(EventParticipant.class);
524+
Root<EventParticipant> epr2 = sourceCaseSubquery.from(EventParticipant.class);
525+
sourceCaseSubquery.select(epr2);
526+
sourceCaseSubquery.where(
527+
cb.equal(epr2.get(EventParticipant.RESULTING_CASE), contactRoot.get(Contact.CAZE)),
528+
cb.equal(epr2.get(EventParticipant.EVENT), epRoot.get(EventParticipant.EVENT)));
529+
530+
contactCQ.multiselect(
531+
epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID),
532+
cb.count(epRoot),
533+
cb.sum(cb.selectCase().when(cb.exists(sourceCaseSubquery), 1).otherwise(0).as(Long.class)));
534+
contactCQ.where(participantPersonEqualsContactPerson, notDeleted, contactNotDeleted);
535+
contactCQ.groupBy(epRoot.get(EventParticipant.EVENT).get(AbstractDomainObject.UUID));
536+
537+
objectQueryList = em.createQuery(contactCQ).getResultList();
538+
if (objectQueryList != null) {
539+
objectQueryList.forEach(r -> {
540+
contactCounts.put((String) r[0], ((Long) r[1]));
541+
contactCountsSourceInEvent.put((String) r[0], ((Long) r[2]));
542+
});
543+
}
512544
}
513545

514546
if (exportList != null) {
515547
for (EventExportDto exportDto : exportList) {
516548
Optional.ofNullable(participantCounts.get(exportDto.getUuid())).ifPresent(exportDto::setParticipantCount);
517549
Optional.ofNullable(caseCounts.get(exportDto.getUuid())).ifPresent(exportDto::setCaseCount);
518550
Optional.ofNullable(deathCounts.get(exportDto.getUuid())).ifPresent(exportDto::setDeathCount);
551+
Optional.ofNullable(contactCounts.get(exportDto.getUuid())).ifPresent(exportDto::setContactCount);
552+
Optional.ofNullable(contactCountsSourceInEvent.get(exportDto.getUuid())).ifPresent(exportDto::setContactCountSourceInEvent);
519553
}
520554
}
521555

sormas-backend/src/main/resources/sql/sormas_schema.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6280,4 +6280,12 @@ ALTER TABLE events_history ADD COLUMN evolutionComment text;
62806280

62816281
INSERT INTO schema_version (version_number, comment) VALUES (311, 'Add evolution date and comment to events #3753');
62826282

6283+
6284+
-- 2020-01-13 Add indexes to optimize event directory performance #3276
6285+
CREATE INDEX IF NOT EXISTS idx_eventparticipant_person_id ON eventparticipant USING hash (person_id);
6286+
CREATE INDEX IF NOT EXISTS idx_eventparticipant_event_id ON eventparticipant USING hash (event_id);
6287+
CREATE INDEX IF NOT EXISTS idx_contact_person_id ON contact USING hash (person_id);
6288+
6289+
INSERT INTO schema_version (version_number, comment) VALUES (312, 'Add indexes to optimize event directory performance #3276');
6290+
62836291
-- *** Insert new sql commands BEFORE this line ***
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package de.symeda.sormas.ui.events;
2+
3+
public enum EventContactCountMethod {
4+
ALL,
5+
SOURCE_CASE_IN_EVENT,
6+
BOTH_METHODS
7+
}

0 commit comments

Comments
 (0)