Query Cookbook
Ready-made SQL for common workflows. All queries follow the
conventions: versioned tables are filtered to
is_current = TRUE, and year_rank = 1 selects each entity’s most recent
year. Replace the example identifiers with your own.
Map my portfolio to the Net Purpose universe
WITH portfolio AS (
SELECT column1 AS isin
FROM VALUES ('US0378331005'), ('GB0007980591'), ('NL0000009165')
)
SELECT p.isin,
e.entity_id,
e.name,
c.has_outcomes,
c.has_sdi_revenue
FROM portfolio p
LEFT JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.primary_isin = p.isin AND e.is_current = TRUE
LEFT JOIN NET_PURPOSE.ENTITIES.ENTITIES_DATA_COVERAGE c
ON c.entity_id = e.entity_id AND c.is_current = TRUE;Rows with a NULL entity_id are outside the universe. From here on, keep
the resolved entity_id list in a temporary table or CTE.
What data exists for my portfolio?
entities_data_coverage summarises, per entity, which datasets have
something to offer before you query them:
SELECT e.name,
c.has_outcomes,
c.has_research,
c.has_estimated_data,
c.has_revenue,
c.has_sdi_revenue
FROM NET_PURPOSE.ENTITIES.ENTITIES_DATA_COVERAGE c
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = c.entity_id AND e.is_current = TRUE
WHERE c.is_current = TRUE
AND c.entity_id IN (12345, 23456, 34567);Latest value of one question for my portfolio
SELECT e.name,
ad.year,
ad.value,
ad.data_type,
ad.is_not_found
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = ad.entity_id AND e.is_current = TRUE
WHERE ad.is_current = TRUE
AND ad.canonical_question_id = 'co2_total_1_2_and_3'
AND ad.year_rank = 1
AND ad.entity_id IN (12345, 23456, 34567);Time series for one company and question
SELECT year, value, yoy_delta, yoy_perc, data_type
FROM NET_PURPOSE.OUTCOMES.ALL_DATA
WHERE is_current = TRUE
AND entity_id = 12345
AND canonical_question_id = 'co2_total_1_2_and_3'
ORDER BY year;Disclosure rate: how many companies report a question?
all_data records researched-but-not-disclosed answers explicitly
(is_not_found = TRUE), so disclosure rates are a single aggregation:
SELECT q.name,
COUNT(*) AS researched,
COUNT_IF(NOT ad.is_not_found) AS disclosed,
ROUND(100 * COUNT_IF(NOT ad.is_not_found)
/ COUNT(*), 1) AS disclosure_pct
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.REFERENCE.QUESTIONS q ON q.question_id = ad.question_id
WHERE ad.is_current = TRUE
AND ad.year_rank = 1
GROUP BY q.name
ORDER BY disclosure_pct DESC;Reported-only view (exclude estimates)
SELECT *
FROM NET_PURPOSE.OUTCOMES.ALL_DATA
WHERE is_current = TRUE
AND data_origin_class_id = 1; -- 1 = Reported, 2 = Derived, 3 = EstimatedHow much of my data is estimated?
The same data_origin_class_id label supports a per-entity mix, useful for
understanding how much of an analysis rests on modelled values:
SELECT e.name,
COUNT(*) AS datapoints,
ROUND(100 * COUNT_IF(ad.data_origin_class_id = 1)
/ COUNT(*), 1) AS reported_pct,
ROUND(100 * COUNT_IF(ad.data_origin_class_id = 3)
/ COUNT(*), 1) AS estimated_pct
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = ad.entity_id AND e.is_current = TRUE
WHERE ad.is_current = TRUE
AND ad.year_rank = 1
AND ad.is_not_found = FALSE
AND ad.entity_id IN (12345, 23456, 34567)
GROUP BY e.name;Benchmark a company against its sector
Rank every company in the target’s sector on one question and read off the percentile:
WITH sector_values AS (
SELECT ad.entity_id,
e.name,
ad.value
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = ad.entity_id AND e.is_current = TRUE
WHERE ad.is_current = TRUE
AND ad.canonical_question_id = 'co2_total_1_2_and_3'
AND ad.year_rank = 1
AND ad.value IS NOT NULL
AND e.sector = (
SELECT sector FROM NET_PURPOSE.ENTITIES.ENTITIES
WHERE entity_id = 12345 AND is_current = TRUE
)
)
SELECT name,
value,
ROUND(100 * PERCENT_RANK() OVER (ORDER BY value), 1) AS percentile
FROM sector_values
QUALIFY entity_id = 12345; -- remove to list the whole sectorThe percentile is computed across the whole sector before the QUALIFY
filter narrows the output to the company of interest.
Portfolio-weighted intensity
Combine holding weights with the pre-computed intensity columns on
all_data (evic_per_million, revenue_per_million):
WITH holdings AS (
SELECT column1 AS entity_id, column2 AS weight
FROM VALUES (12345, 0.40), (23456, 0.35), (34567, 0.25)
)
SELECT SUM(h.weight * ad.evic_per_million) AS weighted_intensity_per_musd_evic
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN holdings h ON h.entity_id = ad.entity_id
WHERE ad.is_current = TRUE
AND ad.canonical_question_id = 'co2_total_1_2_and_3'
AND ad.year_rank = 1
AND ad.is_not_found = FALSE;SDG-aligned revenue for a company
Join taxonomy revenue to each taxon’s primary SDG assignment in
taxonomy_sdgs and roll up to goals:
SELECT ts.goal AS sdg_goal,
SUM(etr.revenue_amount) AS revenue_usd,
SUM(etr.revenue_pct) AS revenue_pct
FROM NET_PURPOSE_REVENUE.REVENUE.ENTITY_TAXONOMY_REVENUE etr
JOIN NET_PURPOSE.REFERENCE.TAXONOMY_SDGS ts
ON ts.taxon_id = etr.taxon_id
AND ts.is_primary = TRUE
WHERE etr.is_current = TRUE
AND etr.entity_id = 12345
AND etr.year_rank = 1
GROUP BY ts.goal
ORDER BY revenue_pct DESC;Drop the is_primary filter to include secondary SDG contributions as
well (a taxon can then count towards several goals).
Revenue exposure to one taxonomy branch, across a portfolio
The path column on taxonomy makes it easy to
select a whole branch of the tree — a theme and everything underneath it:
SELECT e.name,
etr.year,
SUM(etr.revenue_amount) AS branch_revenue_usd,
SUM(etr.revenue_pct) AS branch_revenue_pct
FROM NET_PURPOSE_REVENUE.REVENUE.ENTITY_TAXONOMY_REVENUE etr
JOIN NET_PURPOSE.REFERENCE.TAXONOMY t
ON t.taxon_id = etr.taxon_id
AND t.path LIKE (SELECT path FROM NET_PURPOSE.REFERENCE.TAXONOMY WHERE taxon_id = 42) || '%'
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = etr.entity_id AND e.is_current = TRUE
WHERE etr.is_current = TRUE
AND etr.year_rank = 1
AND etr.entity_id IN (12345, 23456, 34567)
GROUP BY e.name, etr.year
ORDER BY branch_revenue_pct DESC;SDI screen for a portfolio
SELECT e.name,
e.primary_isin,
sc.year,
sc.sdi_classification,
sc.is_sdi_decisive
FROM NET_PURPOSE_REVENUE.REVENUE.SDI_CLASSIFICATIONS sc
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = sc.entity_id AND e.is_current = TRUE
WHERE sc.is_current = TRUE
AND sc.entity_id IN (12345, 23456, 34567)
QUALIFY ROW_NUMBER() OVER (
PARTITION BY sc.entity_id ORDER BY sc.year DESC
) = 1;Audit a number back to its source document
Start from any all_data row and follow the origins:
SELECT ad.canonical_question_id,
ad.year,
ad.value,
sf.recorded_value,
src.name AS source_document,
src.url,
sf.page_num
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.OUTCOMES.ALL_DATA_ORIGINS ao
ON ao.all_data_id = ad.all_data_id AND ao.is_current = TRUE
JOIN NET_PURPOSE.FOUNDATIONS.OUTCOMES_FACTS f
ON f.fact_id = ao.fact_id AND f.is_current = TRUE
JOIN NET_PURPOSE.FOUNDATIONS.SUPPORTING_FACTS sf
ON sf.fact_id = f.fact_id AND sf.is_current = TRUE
LEFT JOIN NET_PURPOSE.REFERENCE.SOURCES src
ON src.source_id = sf.source_id
WHERE ad.is_current = TRUE
AND ad.entity_id = 12345
AND ad.canonical_question_id = 'co2e_avoided'
AND ad.year = 2024;For calculated values, follow ao.kpi_result_id into kpi_results and
kpi_calculation_trees instead; for estimated values, query
estimation_lifecycle for the same entity, taxon and year.
Unpack a calculated KPI into its formula and inputs
SELECT kr.canonical_question_id,
kr.year,
kr.value,
kr.unit,
kf.formula,
kf.formula_description,
ct.calculation_tree
FROM NET_PURPOSE.FOUNDATIONS.KPI_RESULTS kr
JOIN NET_PURPOSE.REFERENCE.KPI_FORMULAE kf
ON kf.question_id = kr.question_id
JOIN NET_PURPOSE.FOUNDATIONS.KPI_CALCULATION_TREES ct
ON ct.kpi_result_id = kr.kpi_result_id AND ct.is_current = TRUE
WHERE kr.is_current = TRUE
AND kr.entity_id = 12345
AND kr.canonical_question_id = 'co2_total_1_2_and_3'
ORDER BY kr.year;Has this number been restated?
Because tables are append-only, the full version history of a value is one
query away — just drop the is_current filter:
SELECT year,
value,
data_type,
valid_from,
valid_to,
is_current
FROM NET_PURPOSE.OUTCOMES.ALL_DATA
WHERE entity_id = 12345
AND canonical_question_id = 'co2_total_1_2_and_3'
AND year = 2023
ORDER BY valid_from;More than one row means the value changed after first publication — for example a company restatement or a research revision.
How fresh is the data for my portfolio?
SELECT e.name,
MAX(ad.year) AS latest_year,
MAX(ad.end_date) AS latest_period_end
FROM NET_PURPOSE.OUTCOMES.ALL_DATA ad
JOIN NET_PURPOSE.ENTITIES.ENTITIES e
ON e.entity_id = ad.entity_id AND e.is_current = TRUE
WHERE ad.is_current = TRUE
AND ad.is_not_found = FALSE
AND ad.entity_id IN (12345, 23456, 34567)
GROUP BY e.name
ORDER BY latest_year;What did the data look like on a past date?
Replace the is_current filter with a validity window (see
Conventions):
SELECT year, value
FROM NET_PURPOSE.OUTCOMES.ALL_DATA
WHERE entity_id = 12345
AND canonical_question_id = 'co2_total_1_2_and_3'
AND valid_from <= '2025-06-30'::TIMESTAMP_TZ
AND valid_to > '2025-06-30'::TIMESTAMP_TZ
ORDER BY year;