Skip to content

Performance: observation_event - #974

Merged
mpeels merged 4 commits into
rel-7.13from
mp/APP-788/observation-stored-proc
Aug 11, 2026
Merged

Performance: observation_event#974
mpeels merged 4 commits into
rel-7.13from
mp/APP-788/observation-stored-proc

Conversation

@mpeels

@mpeels mpeels commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

Updates the observation_event stored proc and adds a unit test for coverage. The unit test was tested against the pre-optimized stored procedure and post-optimized stored proc with both passing.

Related Issue

APP-899

Detailed Summary

This PR restructures sp_observation_event from a set of deeply nested, per-row correlated subqueries into a CTE-driven query. The output shape and columns are unchanged — this is a performance and maintainability refactor, not a functional rewrite.
Key Optimizations

  1. Replaced manually-unrolled 4-hop relationship walk with a recursive CTE
    The original computed followup_observations using four separate UNION-joined subqueries, each one hard-coding one more hop of act_relationship joins (act1, act1→act2, act1→act2→act3, act1→act2→act3→act4). This subquery was re-evaluated once per output row inside the OUTER APPLY.
    The updated version replaces this with a single recursive CTE (related_acts) bounded by OPTION (MAXRECURSION 4), computed once for the whole batch and then joined via resolved_followups. This avoids redundant re-scans of act_relationship for every observation row and scales much better as the input id list grows.
  2. Pre-computed batch-level CTEs instead of per-row correlated subqueries
    resolved_phc_uids, resolved_followups, and resolved_person_participations are now computed once as CTEs ahead of the main SELECT, rather than being recomputed as correlated subqueries inside OUTER APPLY for each observation. This lets the optimizer build a single set-based plan instead of executing the same logic N times (once per row).
  3. Narrowed the person-enrichment lookup to relevant persons only
    The original joined the full person → person_name → entity_id → role → code_value_general chain for every person in the table, relying on the outer join to filter down to relevant rows afterward — meaning the fan-out (entity_id/role joins) was computed broadly before being filtered.
    The updated version introduces a relevant_persons CTE that first identifies the distinct person_uids actually referenced by ACTIVE participations on the requested observations, then restricts the enrichment join to just those persons (WHERE person.person_uid IN (SELECT person_uid FROM relevant_persons)). This is a pure filter push-down — same results, far less work when the person table is large.
  4. Single, canonical id parsing
    The original called STRING_SPLIT(@obs_id_list, ',') inline in the final WHERE clause with an implicit type comparison. The updated version parses the input once into a base_ids CTE with an explicit CAST(... AS BIGINT), and every downstream CTE/join reuses that single canonical set — removing repeated parsing and making the join type explicit.
  5. Simplified query shape
    The original wrapped the driving Observation table plus its OUTER APPLY in a derived table (... AS results) which was then joined to act and observation_interp. The updated version joins act/observation_interp directly to Observation, with the id-list filter applied straight to the driving table (WHERE o.observation_uid IN (SELECT observation_uid FROM base_ids)). This removes a layer of indirection and makes the query easier for the optimizer (and for humans) to reason about.
  6. Cleanup of dead code
    Commented-out blocks for ldf_observation and associated_investigations (already unused/commented in the original) were removed rather than carried forward, reducing noise.

Checklist

  • I have ensured that the pull request is of a manageable size, allowing it to be reviewed within a single session.
  • I have reviewed my changes to ensure they are clear, concise, and well-documented.
  • I have updated the documentation, if applicable.
  • I have added or updated test cases to cover my changes, if applicable.

@mpeels mpeels changed the title Update observation event stored proc Draft - Update observation event stored proc Jul 29, 2026
@mpeels mpeels changed the title Draft - Update observation event stored proc Update observation_event stored proc Jul 29, 2026
@mpeels
mpeels marked this pull request as ready for review July 29, 2026 23:28
@mpeels
mpeels requested a review from a team as a code owner July 29, 2026 23:28
@mpeels mpeels changed the title Update observation_event stored proc Performance: observation_event Jul 30, 2026
eliSkylight
eliSkylight previously approved these changes Aug 10, 2026

@eliSkylight eliSkylight left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the CTE approach is neatly organized. thanks!

@mpeels
mpeels merged commit aa84913 into rel-7.13 Aug 11, 2026
3 checks passed
@mpeels
mpeels deleted the mp/APP-788/observation-stored-proc branch August 11, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants