initial commit

This commit is contained in:
Andrew Charlwood
2026-05-12 16:40:03 +01:00
commit 647d1bfa7f
38 changed files with 2715 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Copied Reference Queries
These files were copied from the working `Snowflake-Queries` repo because they were already explicitly template-like.
Use these as reference examples, not as the first place to start a new query. Several contain historic assumptions such as Norfolk and Waveney geography, hardcoded SNOMED codes, or notes that were current when the original work was done.
For new work, start from the cleaned templates in the numbered folders at the repo root.
## Contents
- `medicine_lookup_checks/`: original short checks for prescribing or dispensing by VTM or VMP.
- `pqs_long_format/`: original PQS long-format and rolling-period templates.
- `useful_short_queries/`: original compact lookup, freshness, price/unit, and practice-list queries.
The original `CheckPrescribingByPseudo.sql` file was deliberately not copied because it contains a hardcoded patient pseudonym.
@@ -0,0 +1,27 @@
-- Snowflake version: Check dispensing by VMP (Virtual Medicinal Product)
-- Uses GPMeds dispensing data from NATIONAL.GPMED
WITH SnomedCodes AS (
SELECT "ProductSnomedCode"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "MedicinalLatestSnomedCode" = '40326811000001109' -- Specific VMP
),
LatestPeriod AS (
SELECT MAX("ProcessingPeriodDate") AS MaxPeriodDate
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare"
)
SELECT
gpm."ProcessingPeriodDate",
dos."OrganisationName",
COUNT(DISTINCT gpm."PatientPseudonym") AS UniquePatientCount,
SUM(gpm."ItemCount") AS TotalItemsDispensed,
SUM(gpm."PaidQuantity") AS TotalQuantityDispensed
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare" gpm
INNER JOIN SnomedCodes sc ON gpm."PaiddmdCode" = sc."ProductSnomedCode"
LEFT JOIN DATA_HUB.DWH."DimOrganisationAndSite" dos
ON gpm."CostCentreODSCode" = dos."SiteCode"
CROSS JOIN LatestPeriod lp
WHERE gpm."ProcessingPeriodDate" > DATEADD(MONTH, -12, lp.MaxPeriodDate)
AND gpm."ProcessingPeriodDate" <= lp.MaxPeriodDate
GROUP BY gpm."ProcessingPeriodDate", dos."OrganisationName", gpm."CostCentreODSCode"
ORDER BY gpm."ProcessingPeriodDate", dos."OrganisationName";
@@ -0,0 +1,29 @@
-- Snowflake version: Check dispensing by VTM (Virtual Therapeutic Moiety)
-- Uses GPMeds dispensing data from NATIONAL.GPMED
WITH SnomedCodes AS (
SELECT "ProductSnomedCode", "ProductDescription"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "TherapeuticMoietySnomedCode" = '41145911000001106' -- Tirzepatide VTM
),
LatestPeriod AS (
SELECT MAX("ProcessingPeriodDate") AS MaxPeriodDate
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare"
)
SELECT *
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare" gpm
INNER JOIN SnomedCodes sc ON gpm."PaiddmdCode" = sc."ProductSnomedCode"
CROSS JOIN LatestPeriod lp
WHERE gpm."ProcessingPeriodDate" > DATEADD(MONTH, -1, lp.MaxPeriodDate)
AND gpm."ProcessingPeriodDate" <= lp.MaxPeriodDate
limit 100;
SELECT
COUNT(DISTINCT gpm."PatientPseudonym") AS UniquePatientCount,
MAX(gpm."ProcessingPeriodDate") AS LatestPeriod
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare" gpm
INNER JOIN SnomedCodes sc ON gpm."PaiddmdCode" = sc."ProductSnomedCode"
CROSS JOIN LatestPeriod lp
WHERE gpm."ProcessingPeriodDate" > DATEADD(MONTH, -1, lp.MaxPeriodDate)
AND gpm."ProcessingPeriodDate" <= lp.MaxPeriodDate;
@@ -0,0 +1,19 @@
-- Snowflake version: Check prescribing by VMP (Virtual Medicinal Product)
-- Uses unified PrimaryCareMedication table (combines EMIS + TPP)
SET StartDate = '2025-04-01';
SET EndDate = '2025-07-31';
WITH SnomedCodes AS (
SELECT "ProductSnomedCode"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "MedicinalLatestSnomedCode" = '40326811000001109' -- Specific VMP
)
SELECT DISTINCT
pcm."PatientPseudonym",
pcm."DateMedicationStart" AS EffectiveDate,
CAST(pcm."Quantity" AS VARCHAR(20)) AS Quantity
FROM DATA_HUB.PHM."PrimaryCareMedication" pcm
INNER JOIN SnomedCodes sc ON pcm."SNOMEDCode" = sc."ProductSnomedCode"
WHERE pcm."DateMedicationStart" BETWEEN $StartDate AND $EndDate
AND pcm."PatientPseudonym" IS NOT NULL;
@@ -0,0 +1,30 @@
-- Snowflake version: Check prescribing by VTM (Virtual Therapeutic Moiety)
-- Uses unified PrimaryCareMedication table (combines EMIS + TPP)
SET StartDate = '2025-11-01';
SET EndDate = '2025-12-31';
WITH SnomedCodes AS (
SELECT "ProductSnomedCode", "ProductDescription"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "TherapeuticMoietySnomedCode" = '41145911000001106' -- Tirzepatide VTM
)
SELECT DISTINCT
dos."OrganisationName",
dp."PersonKey",
pcm."DateMedicationStart" AS EffectiveDate,
sc."ProductDescription",
CAST(pcm."Quantity" AS VARCHAR(20)) AS Quantity,
pcm."QuantityText" AS Dosage
FROM DATA_HUB.PHM."PrimaryCareMedication" pcm
INNER JOIN DATA_HUB.DWH."DimPerson" dp
ON pcm."PatientPseudonym" = dp."PatientPseudonym"
INNER JOIN SnomedCodes sc
ON pcm."SNOMEDCode" = sc."ProductSnomedCode"
INNER JOIN DATA_HUB.DWH."DimOrganisationAndSite" dos
ON dp."CurrentGeneralPractice" = dos."SiteCode"
WHERE pcm."DateMedicationStart" BETWEEN $StartDate AND $EndDate
AND pcm."PatientPseudonym" IS NOT NULL
AND dos."OrganisationSubType" = 'GP Practice'
AND dos."IsSiteNorfolkAndWaveney" = 'Yes'
AND dos."IsSiteActive" = 'Yes';
@@ -0,0 +1,28 @@
-- Snowflake version: Check prescribing by VTM (Virtual Therapeutic Moiety)
-- Uses unified PrimaryCareMedication table (combines EMIS + TPP)
SET StartDate = '2025-05-01';
SET EndDate = '2025-11-30';
WITH SnomedCodes AS (
SELECT "ProductSnomedCode", "ProductDescription"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "TherapeuticMoietySnomedCode" = '41145911000001106' -- Tirzepatide VTM
)
SELECT
DATE_TRUNC('MONTH', pcm."DateMedicationStart") AS PrescribingMonth,
COUNT(DISTINCT dp."PersonKey") AS UniquePatientCount
FROM DATA_HUB.PHM."PrimaryCareMedication" pcm
INNER JOIN DATA_HUB.DWH."DimPerson" dp
ON pcm."PatientPseudonym" = dp."PatientPseudonym"
INNER JOIN SnomedCodes sc
ON pcm."SNOMEDCode" = sc."ProductSnomedCode"
INNER JOIN DATA_HUB.DWH."DimOrganisationAndSite" dos
ON dp."CurrentGeneralPractice" = dos."SiteCode"
WHERE pcm."DateMedicationStart" BETWEEN $StartDate AND $EndDate
AND pcm."PatientPseudonym" IS NOT NULL
AND dos."OrganisationSubType" = 'GP Practice'
AND dos."IsSiteNorfolkAndWaveney" = 'Yes'
AND dos."IsSiteActive" = 'Yes'
GROUP BY DATE_TRUNC('MONTH', pcm."DateMedicationStart")
ORDER BY PrescribingMonth
@@ -0,0 +1,34 @@
-- Snowflake version: Check prescribing by VTM for last 3 months
-- Uses unified PrimaryCareMedication table (combines EMIS + TPP)
-- Dynamically calculates date range from latest data
WITH LatestDate AS (
SELECT DATEADD(DAY, 1, MAX("DateMedicationStart")::DATE) AS EndDate
FROM DATA_HUB.PHM."PrimaryCareMedication"
WHERE "DateMedicationStart" >= DATEADD(MONTH, -6, CURRENT_DATE())
),
DateRange AS (
SELECT
EndDate,
DATEADD(MONTH, -3, EndDate) AS StartDate
FROM LatestDate
),
SnomedCodes AS (
SELECT "ProductSnomedCode", "ProductDescription"
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "TherapeuticMoietySnomedCode" = '775477008' -- Tirzepatide VTM
),
AllPatients AS (
SELECT DISTINCT pcm."PatientPseudonym"
FROM DATA_HUB.PHM."PrimaryCareMedication" pcm
INNER JOIN SnomedCodes sc ON pcm."SNOMEDCode" = sc."ProductSnomedCode"
CROSS JOIN DateRange dr
WHERE pcm."DateMedicationStart" > dr.StartDate
AND pcm."DateMedicationStart" <= dr.EndDate
AND pcm."PatientPseudonym" IS NOT NULL
)
SELECT
COUNT(DISTINCT "PatientPseudonym") AS UniquePatientCount,
(SELECT StartDate FROM DateRange) AS StartDate,
(SELECT EndDate FROM DateRange) AS EndDate
FROM AllPatients;
@@ -0,0 +1,90 @@
-- ==============================================================================
-- PERIOD GENERATOR - Phase 7 Long Format Output
-- ==============================================================================
-- This file contains the reusable recursive CTE pattern for generating
-- dynamic rolling periods from June 2025 to the current month.
--
-- Requirements:
-- - Earliest period: ends 2025-06-30 (hardcoded)
-- - Latest period: ends at last day of current month (dynamic)
-- - One period per month from June 2025 to current month
-- - 12-month lookback for M1, M2, M3, M5, M6, M7, M8
-- - 6-month lookback for M4 (Female UTI)
--
-- Example output (run 2026-01-29):
-- 8 periods: Jun 25, Jul 25, Aug 25, Sep 25, Oct 25, Nov 25, Dec 25, Jan 26
-- ==============================================================================
-- ==============================================================================
-- RECURSIVE CTE PATTERN (Copy this into each measure query)
-- ==============================================================================
WITH RECURSIVE date_periods AS (
-- Anchor: first period ends June 2025
SELECT DATE '2025-06-30' AS period_end_date
UNION ALL
-- Recurse: add one month until we reach current month
SELECT LAST_DAY(DATEADD(MONTH, 1, period_end_date))
FROM date_periods
WHERE period_end_date < LAST_DAY(CURRENT_DATE())
),
-- Calculate start dates for both 12-month and 6-month lookbacks
rolling_periods AS (
SELECT
CAST(DATEADD(MONTH, -11, DATE_TRUNC('MONTH', period_end_date)) AS DATE) AS "PeriodStartDate_12m",
CAST(DATEADD(MONTH, -5, DATE_TRUNC('MONTH', period_end_date)) AS DATE) AS "PeriodStartDate_6m",
CAST(period_end_date AS DATE) AS "PeriodEndDate"
FROM date_periods
)
SELECT * FROM rolling_periods ORDER BY "PeriodEndDate";
-- ==============================================================================
-- EXPECTED OUTPUT (as of January 2026)
-- ==============================================================================
-- PeriodEndDate PeriodStartDate_12m PeriodStartDate_6m
-- 2025-06-30 2024-07-01 2025-01-01
-- 2025-07-31 2024-08-01 2025-02-01
-- 2025-08-31 2024-09-01 2025-03-01
-- 2025-09-30 2024-10-01 2025-04-01
-- 2025-10-31 2024-11-01 2025-05-01
-- 2025-11-30 2024-12-01 2025-06-01
-- 2025-12-31 2025-01-01 2025-07-01
-- 2026-01-31 2025-02-01 2025-08-01
--
-- 8 periods total (June 2025 through January 2026)
-- ==============================================================================
-- ==============================================================================
-- USAGE NOTES
-- ==============================================================================
-- 1. For 12-month measures (M1, M2, M3, M5, M6, M7, M8):
-- Filter data WHERE date_column BETWEEN "PeriodStartDate_12m" AND "PeriodEndDate"
--
-- 2. For 6-month measure (M4):
-- Filter data WHERE date_column BETWEEN "PeriodStartDate_6m" AND "PeriodEndDate"
--
-- 3. The recursive CTE is dynamic:
-- - Running in January 2026 → 8 periods
-- - Running in February 2026 → 9 periods
-- - Running in June 2026 → 13 periods
--
-- 4. This pattern should be included at the START of each measure query
-- before the main data CTEs.
-- ==============================================================================
-- ==============================================================================
-- DATA AVAILABILITY NOTES
-- ==============================================================================
-- DISPENSING (NATIONAL.GPMED.MedicinesDispensedInPrimarycare):
-- Max date: ~2025-07 (updates monthly)
-- Min date: 2018-04
-- Coverage: 7+ years — all periods will have full data
--
-- PRESCRIBING (REPORTING_DATASETS_ICB.SCRATCHPAD.MEDS__UnifiedPrescribingTable):
-- Max date: ~2026-01 (updates frequently)
-- Min date: 2024-01
-- Coverage: ~2 years — earlier periods may have partial/no data
-- Note: Periods with PeriodStartDate before 2024-01 will show no prescribing data
-- ==============================================================================
@@ -0,0 +1,203 @@
-- ==============================================================================
-- ROLLING PERIODS TEMPLATE - Phase 7 Long Format Output
-- ==============================================================================
-- This file documents the reusable CTE patterns for generating rolling time
-- periods for both dispensing and prescribing data sources.
--
-- Key considerations:
-- - Dispensing data: 2018-04 to 2025-07 (88 months) - full historical coverage
-- - Prescribing data: 2024-01 to 2026-01 (25 months) - LIMITED HISTORY
-- (prescribing period 2 will have partial/no data)
--
-- IMPORTANT: Each data source has its own MAX date, so periods must be
-- calculated separately within each UNION ALL branch.
-- ==============================================================================
-- ==============================================================================
-- PATTERN 1: 12-MONTH ROLLING PERIODS (for M1, M2, M3, M5, M6, M7, M8)
-- ==============================================================================
-- Generates 3 rolling periods covering approximately 3 years:
-- Period 0: Most recent 12 months
-- Period 1: 12-24 months ago
-- Period 2: 24-36 months ago
/*
-- DISPENSING DATA version:
WITH max_dates_disp AS (
SELECT MAX("ProcessingPeriodDate") AS max_date
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare"
),
period_offsets AS (
SELECT 0 AS period_offset UNION ALL
SELECT 1 UNION ALL
SELECT 2
),
rolling_periods_12m AS (
SELECT
period_offset,
CAST(DATEADD(MONTH, -11, DATE_TRUNC('MONTH', DATEADD(MONTH, -12 * period_offset, max_date))) AS DATE) AS period_start_date,
CAST(LAST_DAY(DATEADD(MONTH, -12 * period_offset, max_date)) AS DATE) AS period_end_date
FROM period_offsets, max_dates_disp
)
-- Then filter data WHERE "ProcessingPeriodDate" BETWEEN period_start_date AND period_end_date
-- Example output (as of July 2025):
-- Period 0: 2024-08-01 to 2025-07-31
-- Period 1: 2023-08-01 to 2024-07-31
-- Period 2: 2022-08-01 to 2023-07-31
*/
-- ==============================================================================
-- PATTERN 2: 6-MONTH ROLLING PERIODS (for M4 - Female UTI)
-- ==============================================================================
-- Generates 6 rolling periods covering approximately 3 years:
-- Period 0: Most recent 6 months
-- Period 1: 6-12 months ago
-- Period 2: 12-18 months ago
-- ...etc
/*
WITH max_dates_disp AS (
SELECT MAX("ProcessingPeriodDate") AS max_date
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare"
),
period_offsets AS (
SELECT 0 AS period_offset UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5
),
rolling_periods_6m AS (
SELECT
period_offset,
CAST(DATEADD(MONTH, -5, DATE_TRUNC('MONTH', DATEADD(MONTH, -6 * period_offset, max_date))) AS DATE) AS period_start_date,
CAST(LAST_DAY(DATEADD(MONTH, -6 * period_offset, max_date)) AS DATE) AS period_end_date
FROM period_offsets, max_dates_disp
)
-- Then filter data WHERE "ProcessingPeriodDate" BETWEEN period_start_date AND period_end_date
-- Example output (as of July 2025):
-- Period 0: 2025-02-01 to 2025-07-31
-- Period 1: 2024-08-01 to 2025-01-31
-- Period 2: 2024-02-01 to 2024-07-31
-- Period 3: 2023-08-01 to 2024-01-31
-- Period 4: 2023-02-01 to 2023-07-31
-- Period 5: 2022-08-01 to 2023-01-31
*/
-- ==============================================================================
-- COMBINED DISPENSING + PRESCRIBING TEMPLATE
-- ==============================================================================
-- Each data source calculates periods from its own MAX date, then combines
-- with UNION ALL. The DataSource column distinguishes them.
/*
WITH
-- =========================
-- DISPENSING PERIODS
-- =========================
max_dates_disp AS (
SELECT MAX("ProcessingPeriodDate") AS max_date
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare"
),
period_offsets AS (
SELECT 0 AS period_offset UNION ALL SELECT 1 UNION ALL SELECT 2
),
disp_periods AS (
SELECT
period_offset,
CAST(DATEADD(MONTH, -11, DATE_TRUNC('MONTH', DATEADD(MONTH, -12 * period_offset, max_date))) AS DATE) AS period_start_date,
CAST(LAST_DAY(DATEADD(MONTH, -12 * period_offset, max_date)) AS DATE) AS period_end_date
FROM period_offsets, max_dates_disp
),
-- =========================
-- PRESCRIBING PERIODS
-- =========================
max_dates_pres AS (
SELECT MAX("DateMedicationStart") AS max_date
FROM REPORTING_DATASETS_ICB.SCRATCHPAD."MEDS__UnifiedPrescribingTable"
WHERE "DateMedicationStart" IS NOT NULL
),
pres_periods AS (
SELECT
period_offset,
CAST(DATEADD(MONTH, -11, DATE_TRUNC('MONTH', DATEADD(MONTH, -12 * period_offset, max_date))) AS DATE) AS period_start_date,
CAST(LAST_DAY(DATEADD(MONTH, -12 * period_offset, max_date)) AS DATE) AS period_end_date
FROM period_offsets, max_dates_pres
),
-- =========================
-- DATA QUERIES (example structure)
-- =========================
disp_data AS (
SELECT
org."OrganisationName",
dp.period_start_date AS "PeriodStartDate",
dp.period_end_date AS "PeriodEndDate",
'Dispensing' AS "DataSource",
-- ... aggregations
FROM NATIONAL.GPMED."MedicinesDispensedInPrimarycare" meds
CROSS JOIN disp_periods dp
JOIN DATA_HUB.DWH."DimOrganisationAndSite" org ON ...
WHERE meds."ProcessingPeriodDate" BETWEEN dp.period_start_date AND dp.period_end_date
GROUP BY org."OrganisationName", dp.period_start_date, dp.period_end_date
),
pres_data AS (
SELECT
org."OrganisationName",
pp.period_start_date AS "PeriodStartDate",
pp.period_end_date AS "PeriodEndDate",
'Prescribing' AS "DataSource",
-- ... aggregations
FROM REPORTING_DATASETS_ICB.SCRATCHPAD."MEDS__UnifiedPrescribingTable" rx
CROSS JOIN pres_periods pp
JOIN DATA_HUB.DWH."DimOrganisationAndSite" org ON ...
WHERE rx."DateMedicationStart" BETWEEN pp.period_start_date AND pp.period_end_date
GROUP BY org."OrganisationName", pp.period_start_date, pp.period_end_date
)
-- =========================
-- UNPIVOT TO LONG FORMAT
-- =========================
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate", "DataSource",
'M1' AS "Measure", 'IndicatorA' AS "Indicator", CAST("IndicatorA" AS FLOAT) AS "Value"
FROM disp_data
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate", "DataSource",
'M1' AS "Measure", 'IndicatorB' AS "Indicator", CAST("IndicatorB" AS FLOAT) AS "Value"
FROM disp_data
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate", "DataSource",
'M1' AS "Measure", 'IndicatorA' AS "Indicator", CAST("IndicatorA" AS FLOAT) AS "Value"
FROM pres_data
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate", "DataSource",
'M1' AS "Measure", 'IndicatorB' AS "Indicator", CAST("IndicatorB" AS FLOAT) AS "Value"
FROM pres_data
*/
-- ==============================================================================
-- DATE RANGE NOTES
-- ==============================================================================
-- As of January 2026:
--
-- DISPENSING (NATIONAL.GPMED.MedicinesDispensedInPrimarycare):
-- Max date: 2025-07-01
-- Min date: 2018-04-01
-- Coverage: 88 months (7+ years)
-- All 3 rolling periods have full data
--
-- PRESCRIBING (REPORTING_DATASETS_ICB.SCRATCHPAD.MEDS__UnifiedPrescribingTable):
-- Max date: 2026-01-06
-- Min date: 2024-01-06
-- Coverage: 25 months (~2 years)
-- Period 0: Full data
-- Period 1: Full data
-- Period 2: PARTIAL OR NO DATA (starts 2023-02, but data begins 2024-01)
--
-- This asymmetry is expected - prescribing data is newer than dispensing.
-- ==============================================================================
@@ -0,0 +1,255 @@
-- ==============================================================================
-- UNIFIED SQL TEMPLATE - Phase 7 Long Format Output
-- ==============================================================================
-- This template shows the complete pattern for converting measures to long format
-- with both dispensing and prescribing data sources and rolling time periods.
--
-- Output columns (7 standard columns):
-- OrganisationName - GP Practice name
-- PeriodStartDate - Start of the time window (DATE type)
-- PeriodEndDate - End of the time window (DATE type)
-- DataSource - 'Dispensing' or 'Prescribing'
-- Measure - Measure identifier (e.g., 'M2')
-- Indicator - Metric name (e.g., 'TotalPrescriptions')
-- Value - The metric value (FLOAT type)
--
-- Key patterns:
-- 1. Recursive CTE generates all periods from June 2025 to current month
-- 2. Cross join practices with periods for all combinations
-- 3. Left join dispensing data filtered by date range per period
-- 4. Inner join prescribing data filtered by date range per period
-- 5. UNION ALL to unpivot wide indicators into long format
-- 6. UNION ALL to combine dispensing and prescribing data sources
-- ==============================================================================
-- ==============================================================================
-- SECTION 1: RECURSIVE PERIOD GENERATOR
-- Copy this section to all measure queries unchanged
-- ==============================================================================
WITH RECURSIVE date_periods AS (
-- Anchor: first period ends June 2025
SELECT DATE '2025-06-30' AS period_end_date
UNION ALL
-- Recurse: add one month until we reach current month
SELECT LAST_DAY(DATEADD(MONTH, 1, period_end_date))
FROM date_periods
WHERE period_end_date < LAST_DAY(CURRENT_DATE())
),
rolling_periods AS (
SELECT
-- For 12-month measures (M1, M2, M3, M5, M6, M7, M8):
CAST(DATEADD(MONTH, -11, DATE_TRUNC('MONTH', period_end_date)) AS DATE) AS "PeriodStartDate",
-- For 6-month measures (M4), use this instead:
-- CAST(DATEADD(MONTH, -5, DATE_TRUNC('MONTH', period_end_date)) AS DATE) AS "PeriodStartDate",
CAST(period_end_date AS DATE) AS "PeriodEndDate"
FROM date_periods
),
-- ==============================================================================
-- SECTION 2: COMMON REFERENCE TABLES
-- Adapt these CTEs based on measure requirements
-- ==============================================================================
practices AS (
-- Norfolk & Waveney GP practices
SELECT DISTINCT "OrganisationCode", "OrganisationName"
FROM DATA_HUB.DWH."DimOrganisationAndSite"
WHERE "IsSiteNorfolkAndWaveney" = 'Yes'
AND "IsSiteActive" = 'Yes'
AND "OrganisationSubType" = 'GP Practice'
),
practice_periods AS (
-- Cross join: all practices × all periods
SELECT
p."OrganisationCode",
p."OrganisationName",
rp."PeriodStartDate",
rp."PeriodEndDate"
FROM practices p
CROSS JOIN rolling_periods rp
),
ooh_providers AS (
-- Out of Hours provider organisations (for prescribing data)
SELECT DISTINCT "OrganisationCode"
FROM DATA_HUB.DWH."DimOrganisationAndSite"
WHERE "IsSiteActive" = 'Yes'
AND ("OrganisationName" LIKE '%INTEGRATED CARE%'
OR "OrganisationName" LIKE '%IC24%'
OR "OrganisationCode" = 'Y02751')
),
patients AS (
-- All patients with a registered GP (adapt age/gender filters per measure)
SELECT p."PersonKey", p."PatientPseudonym", p."PHMGeneralPractice" AS "GP"
FROM DATA_HUB.DWH."DimPerson" p
WHERE p."PHMGeneralPractice" IS NOT NULL
AND p."PHMGeneralPractice" <> '*'
),
-- ==============================================================================
-- SECTION 3: DISPENSING DATA AGGREGATION
-- Adapt the SELECT, JOIN, WHERE, and GROUP BY for each measure
-- ==============================================================================
dispensing_agg AS (
SELECT
pp."OrganisationName",
pp."PeriodStartDate",
pp."PeriodEndDate",
-- Measure-specific aggregations (example: M2 duration)
COUNT(DISTINCT meds."PatientPseudonym") AS "UniquePatients",
COUNT(*) AS "TotalPrescriptions",
SUM(
CASE
WHEN (LEFT(meds."PaidBNFCode", 9) IN ('0501030I0', '0501030Z0') AND meds."PaidQuantity" > 6) THEN 1
WHEN (LEFT(meds."PaidBNFCode", 9) = '0501013B0' AND meds."PaidQuantity" > 15) THEN 1
ELSE 0
END
) AS "PrescriptionsMoreThan5Days"
FROM practice_periods pp
LEFT JOIN NATIONAL.GPMED."MedicinesDispensedInPrimarycare" meds
ON pp."OrganisationCode" = meds."CostCentreODSCode"
AND meds."ProcessingPeriodDate" BETWEEN pp."PeriodStartDate" AND pp."PeriodEndDate"
-- Measure-specific BNF code filter
AND LEFT(meds."PaidBNFCode", 9) IN ('0501030I0', '0501030Z0', '0501013B0')
LEFT JOIN DATA_HUB.DWH."DimPerson" dp
ON dp."PatientPseudonym" = meds."PatientPseudonym"
-- Y02751 exclusion: exclude OOH provider's own registered patients
WHERE (pp."OrganisationCode" <> 'Y02751' OR dp."PHMGeneralPractice" = 'Y02751')
GROUP BY pp."OrganisationName", pp."PeriodStartDate", pp."PeriodEndDate"
-- Only include practices with data
HAVING COUNT(*) > 0
),
dispensing_with_pct AS (
-- Calculate derived metrics (percentages, ratios)
SELECT
"OrganisationName",
"PeriodStartDate",
"PeriodEndDate",
"UniquePatients",
"TotalPrescriptions",
"PrescriptionsMoreThan5Days",
ROUND(100.0 * "PrescriptionsMoreThan5Days" / NULLIF("TotalPrescriptions", 0), 2) AS "PercentageMoreThan5Days"
FROM dispensing_agg
),
-- ==============================================================================
-- SECTION 4: PRESCRIBING DATA AGGREGATION
-- Similar structure to dispensing, but with SNOMED→BNF mapping
-- ==============================================================================
prescribing_agg AS (
SELECT
pp."OrganisationName",
pp."PeriodStartDate",
pp."PeriodEndDate",
-- Measure-specific aggregations
COUNT(DISTINCT patients."PatientPseudonym") AS "UniquePatients",
COUNT(*) AS "TotalPrescriptions",
SUM(
CASE
WHEN (LEFT(med."BNFCode", 9) IN ('0501030I0', '0501030Z0') AND rx."Quantity" > 6) THEN 1
WHEN (LEFT(med."BNFCode", 9) = '0501013B0' AND rx."Quantity" > 15) THEN 1
ELSE 0
END
) AS "PrescriptionsMoreThan5Days"
FROM practice_periods pp
INNER JOIN patients ON patients."GP" = pp."OrganisationCode"
INNER JOIN REPORTING_DATASETS_ICB.SCRATCHPAD."MEDS__UnifiedPrescribingTable" rx
ON rx."PersonKey" = patients."PersonKey"
AND rx."DateMedicationStart" BETWEEN pp."PeriodStartDate" AND pp."PeriodEndDate"
-- SNOMED→BNF mapping via DimMedicineAndDevice
INNER JOIN DATA_HUB.DWH."DimMedicineAndDevice" med
ON rx."SNOMEDCode" = med."ProductSnomedCode"
AND med."BNFCode" IS NOT NULL
-- Measure-specific BNF code filter
AND LEFT(med."BNFCode", 9) IN ('0501030I0', '0501030Z0', '0501013B0')
-- OOH provider filter
INNER JOIN ooh_providers ooh ON ooh."OrganisationCode" = rx."OrgCode"
-- Exclude OOH provider's own registered patients
WHERE NOT (rx."OrgCode" = 'Y02751' AND rx."CurrentGeneralPractice" = 'Y02751')
GROUP BY pp."OrganisationName", pp."PeriodStartDate", pp."PeriodEndDate"
HAVING COUNT(*) > 0
),
prescribing_with_pct AS (
SELECT
"OrganisationName",
"PeriodStartDate",
"PeriodEndDate",
"UniquePatients",
"TotalPrescriptions",
"PrescriptionsMoreThan5Days",
ROUND(100.0 * "PrescriptionsMoreThan5Days" / NULLIF("TotalPrescriptions", 0), 2) AS "PercentageMoreThan5Days"
FROM prescribing_agg
)
-- ==============================================================================
-- SECTION 5: LONG FORMAT OUTPUT
-- Use UNION ALL to:
-- 1. Unpivot each indicator into separate rows
-- 2. Combine dispensing and prescribing data sources
-- ==============================================================================
-- DISPENSING indicators
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Dispensing' AS "DataSource", 'M2' AS "Measure",
'UniquePatients' AS "Indicator",
CAST("UniquePatients" AS FLOAT) AS "Value"
FROM dispensing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Dispensing' AS "DataSource", 'M2' AS "Measure",
'TotalPrescriptions' AS "Indicator",
CAST("TotalPrescriptions" AS FLOAT) AS "Value"
FROM dispensing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Dispensing' AS "DataSource", 'M2' AS "Measure",
'PrescriptionsMoreThan5Days' AS "Indicator",
CAST("PrescriptionsMoreThan5Days" AS FLOAT) AS "Value"
FROM dispensing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Dispensing' AS "DataSource", 'M2' AS "Measure",
'PercentageMoreThan5Days' AS "Indicator",
CAST("PercentageMoreThan5Days" AS FLOAT) AS "Value"
FROM dispensing_with_pct
UNION ALL
-- PRESCRIBING indicators
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Prescribing' AS "DataSource", 'M2' AS "Measure",
'UniquePatients' AS "Indicator",
CAST("UniquePatients" AS FLOAT) AS "Value"
FROM prescribing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Prescribing' AS "DataSource", 'M2' AS "Measure",
'TotalPrescriptions' AS "Indicator",
CAST("TotalPrescriptions" AS FLOAT) AS "Value"
FROM prescribing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Prescribing' AS "DataSource", 'M2' AS "Measure",
'PrescriptionsMoreThan5Days' AS "Indicator",
CAST("PrescriptionsMoreThan5Days" AS FLOAT) AS "Value"
FROM prescribing_with_pct
UNION ALL
SELECT "OrganisationName", "PeriodStartDate", "PeriodEndDate",
'Prescribing' AS "DataSource", 'M2' AS "Measure",
'PercentageMoreThan5Days' AS "Indicator",
CAST("PercentageMoreThan5Days" AS FLOAT) AS "Value"
FROM prescribing_with_pct
ORDER BY "PeriodEndDate", "DataSource", "OrganisationName", "Indicator";
-- ==============================================================================
-- EXPECTED OUTPUT STRUCTURE
-- ==============================================================================
-- For M2 with 4 indicators, 2 data sources, 8 periods, ~100 practices:
-- Rows ≈ 4 indicators × 2 sources × 8 periods × 100 practices = 6,400 rows
--
-- Sample row:
-- | OrganisationName | PeriodStartDate | PeriodEndDate | DataSource | Measure | Indicator | Value |
-- |------------------------|-----------------|---------------|-------------|---------|------------------------|--------|
-- | Acle Medical Partnership | 2024-07-01 | 2025-06-30 | Dispensing | M2 | PercentageMoreThan5Days| 57.04 |
-- ==============================================================================
@@ -0,0 +1,90 @@
-- ============================================================================
-- MEDS__ProductPriceAndUnitLookup
-- Medicine Reference Data: Price Per Unit and Pack Unit Descriptions
-- ============================================================================
-- Derives PricePerUnit and PackUnitDescription for all dm+d product levels
-- For VMPP/AMPP: uses direct values from DimMedicineAndDevice
-- For VMP/AMP: derives from child VMPP/AMPP products
-- - Price: AVG of child prices
-- - Units: MODE (most common) of child units
-- Price priority: DrugTariffPricePerUnit > IndicativePricePerUnit > AnnualCost/AnnualQuantity
-- ============================================================================
WITH PackLevelData AS (
-- Get prices and units for pack-level products (VMPP/AMPP)
SELECT
"ProductSnomedCode",
"ParentPresentationSnomedCode",
"ProductLevel",
"PackUnitDescription",
COALESCE(
"DrugTariffPricePerUnit",
"IndicativePricePerUnit",
CASE WHEN "AnnualQuantity" > 0 THEN "AnnualCost" / "AnnualQuantity" END
) AS PricePerUnit
FROM DATA_HUB.DWH."DimMedicineAndDevice"
WHERE "ProductLevel" IN ('VMPP', 'AMPP')
),
ProductPrices AS (
-- Direct prices for VMPP/AMPP
SELECT
"ProductSnomedCode",
PricePerUnit
FROM PackLevelData
WHERE PricePerUnit IS NOT NULL
UNION ALL
-- Aggregated prices for VMP/AMP from their children
SELECT
parent."ProductSnomedCode",
AVG(pld.PricePerUnit) AS PricePerUnit
FROM DATA_HUB.DWH."DimMedicineAndDevice" parent
JOIN PackLevelData pld ON pld."ParentPresentationSnomedCode" = parent."ProductSnomedCode"
WHERE parent."ProductLevel" IN ('VMP', 'AMP')
AND pld.PricePerUnit IS NOT NULL
GROUP BY parent."ProductSnomedCode"
),
PackUnits AS (
-- Direct units for VMPP/AMPP
SELECT
"ProductSnomedCode",
"PackUnitDescription"
FROM PackLevelData
WHERE "PackUnitDescription" IS NOT NULL
UNION ALL
-- Units for VMP/AMP: use most common unit from child VMPP/AMPP products
SELECT
parent."ProductSnomedCode",
MODE(pld."PackUnitDescription") AS "PackUnitDescription"
FROM DATA_HUB.DWH."DimMedicineAndDevice" parent
JOIN PackLevelData pld ON pld."ParentPresentationSnomedCode" = parent."ProductSnomedCode"
WHERE parent."ProductLevel" IN ('VMP', 'AMP')
AND pld."PackUnitDescription" IS NOT NULL
GROUP BY parent."ProductSnomedCode"
)
-- ============================================================================
-- Usage: Join these CTEs to prescribing data on ProductSnomedCode
-- ============================================================================
-- Example:
-- SELECT
-- prescribing.*,
-- pp.PricePerUnit,
-- pu."PackUnitDescription",
-- ROUND(pp.PricePerUnit * prescribing."Quantity", 2) AS EstPrice
-- FROM [prescribing_data] prescribing
-- LEFT JOIN ProductPrices pp ON prescribing."SNOMEDCode" = pp."ProductSnomedCode"
-- LEFT JOIN PackUnits pu ON prescribing."SNOMEDCode" = pu."ProductSnomedCode"
-- ============================================================================
SELECT
pp."ProductSnomedCode",
pp.PricePerUnit,
pu."PackUnitDescription"
FROM ProductPrices pp
LEFT JOIN PackUnits pu ON pp."ProductSnomedCode" = pu."ProductSnomedCode"
@@ -0,0 +1,22 @@
-- =============================================================================
-- Run BOTH queries and import into a sheet called "Lookup":
-- - Column A = Brand Name (from Query 1)
-- - Column C = Generic Name (from Query 2)
-- =============================================================================
-- QUERY 1: Brand Names -> paste into Column A of "Lookup" sheet
SELECT DISTINCT
mad."ProductDescription" AS "Brand Name"
FROM DATA_HUB.DWH."DimMedicineAndDevice" mad
WHERE mad."ProductDescription" IS NOT NULL
ORDER BY mad."ProductDescription";
-- QUERY 2: Generic Names -> paste into Column C of "Lookup" sheet
SELECT DISTINCT
gen."ProductDescription" AS "Generic Name"
FROM DATA_HUB.DWH."DimMedicineAndDevice" mad
INNER JOIN DATA_HUB.DWH."DimMedicineAndDevice" gen
ON mad."MedicinalLatestSnomedCode" = gen."ProductSnomedCode"
WHERE gen."ProductDescription" IS NOT NULL
ORDER BY gen."ProductDescription";
@@ -0,0 +1,9 @@
-- Latest Data Query for Snowflake
-- Returns the most recent prescribing system event date.
-- DateMedicationStart can include future-dated medication records, so this uses
-- DateEventRecorded as the data freshness marker.
SELECT
MAX(CAST("DateEventRecorded" AS DATE)) AS "LatestEventRecordedDate"
FROM PRIMARY_CARE.TPP."SRPrimaryCareMedication"
WHERE "DateEventRecorded" >= DATEADD('MONTH', -3, CURRENT_DATE());
@@ -0,0 +1,24 @@
/*
================================================================================
HF_07_PracticeList.sql
Distinct list of GP Practices in Norfolk & Waveney ICB
================================================================================
PURPOSE:
Reference list of GP practice codes and names for the heart failure analysis.
CREATED: 2026-01-13
================================================================================
*/
SELECT DISTINCT
org."SiteCode" AS "Practice Code",
org."OrganisationName" AS "Practice Name",
org."PlaceName" AS "Place",
org."PCNName" AS "PCN"
FROM DATA_HUB.DWH."DimOrganisationAndSite" org
WHERE org."IsSiteActive" = 'Yes'
AND org."SiteType" = 'Parent'
AND org."IsSiteNorfolkAndWaveney" = 'Yes'
AND org."OrganisationSubType" = 'GP Practice'
ORDER BY org."OrganisationName";
@@ -0,0 +1,12 @@
-- REGISTERED POPULATION: Practice population counts for all Norfolk & Waveney GP practices
-- Returns OrganisationName and RegisteredPopulation for reporting and per-capita calculations
SELECT DISTINCT
"OrganisationName",
"RegisteredPopulation"
FROM DATA_HUB.DWH."DimOrganisationAndSite"
WHERE "IsSiteNorfolkAndWaveney" = 'Yes'
AND "IsSiteActive" = 'Yes'
AND "OrganisationSubType" = 'GP Practice'
AND "OrganisationName" <> 'Vulnerable Adults Service'
ORDER BY "OrganisationName"