Queries · q176 · Head of B2B Commerce · week 1, night 2
“Restricted to genuine new-customer acquisition deals (excluding renewals), what reliably differs between won and lost opportunities — deal size, activities before close, days open, or concentration among accounts and reps?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 1; shared with Samuel Whitcombe, Head of Merchant Success
B2B merchants say their sales teams struggle. How do their pipelines convert from lead to won deal, how long does it take, and what predicts winning or losing?
The manager, at the start of week 1
Note to Daniel Kim — B2B pipeline health
Daniel,
Samuel and I need to give the B2B wholesale merchants (and our own leadership) a clear-eyed view of why their sales teams are struggling to close business. This feeds a joint recommendation Samuel and I are making next week on where to invest — better pipeline coaching/tooling for merchants vs. changes to how we structure stages, quotes and terms in the product. So I want the picture to be accurate more than exhaustive — pick your battles across the nights you have.
Here's what I actually want to know, in plain terms:
Priority order is 1, 2, 3 first — those directly shape the recommendation. 4 and 5 are valuable but can slip if you run out of slots this week.
Keep each report to one clean question — I'd rather have four sharp answers than one crowded one. Flag anything that looks like a data quality issue rather than a real trend.
Thanks — looking forward to the Friday memo.
Patrick
Subject: A few questions from the Merchant Success side
Hi Daniel,
Good to see this getting attention this week. A few things I'd love your analysis to help answer, from the merchant-health angle:
Happy to compare notes before you finalize anything for Patrick.
Best, Samuel
The analyst · week 1, night 2, request 2 of 3
Night 1's big finding is a data-model gotcha, not yet a business trend: every wholesale tenant's 'New business' pipeline shows zero opportunities (open or closed) at Prospecting/Qualification/Proposal/Negotiation — closed deals only ever show as the terminal Closed Won/Closed Lost stage. That means current stage_id can't answer 'where do deals die' for lost opportunities; it only tells us the final outcome. It also means Report 3 (won/lost) and Report 2 (cycle time) from night 1 silently pooled two very different populations together: 'New business' (true lead-to-signed-deal acquisition, which is what Patrick and Samuel are actually asking about) and 'Renewals' (existing-customer renew/churn, where 'Renewed' carries a 1.0 default win probability and is a fundamentally easier motion). Tonight I'm spending all three slots fixing this at the root: (1) reconstruct the real graveyard stage from change_history for New-business-only lost opportunities, (2) redo the won/lost characteristics comparison scoped to New business only, (3) redo the cycle-time trend scoped to New business only. This keeps priorities 1-2-3 intact but makes them actually answer the question Patrick asked, rather than a renewals-diluted version of it. I'm holding Samuel's stale-pipeline-risk question and Patrick's #4/#5 for night 3, once I see whether change_history actually has usable coverage for the graveyard question (flagged as a risk in request 1) and whether the split reports change the picture.
The engineer · 1 dry run on the empty schema, then the submission
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= date(bo.closed_at) ORDER BY er.as_of DESC LIMIT 1 ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id, owr.account_id, owr.owner_user_id, owr.is_won, owr.amount / owr.eur_rate AS amount_eur, EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, round(avg(amount_eur)::numeric, 2) AS avg_amount_eur, round(percentile_cont(0.5) WITHIN GROUP (ORDER BY amount_eur)::numeric, 2) AS median_amount_eur, round(avg(activity_count)::numeric, 2) AS avg_activities_before_close, round(avg(days_open)::numeric, 1) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END
“Query compiles and returns exactly the requested columns. Submitting.”
“Submitted. The report returns one row per outcome (won/lost) for New-business-pipeline opportunities only, with opportunity count, average/median EUR amount, average pre-close activities, average days open, and distinct account/rep counts — mirroring the prior comparison but now isolating genuine new-customer acquisition deals from renewals.”
As Daniel Kim saw it, from the small dataset the agents worked on
outcome | opportunities | avg_amount_eur | median_amount_eur | avg_activities_before_close | avg_days_open | distinct_accounts | distinct_reps --------+---------------+----------------+-------------------+-----------------------------+---------------+-------------------+-------------- won | 155 | 7097.90 | 5880.52 | 2.30 | 9.1 | 155 | 10 lost | 30 | 5628.59 | 6520.14 | 3.37 | 35.5 | 30 | 9 (2 rows)
The memo and the queries that followed; links found by title, so inferred
Later queries that build on this report:
It builds on:
Changed only where the benchmark needs the same result on every system
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%'),new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business'),base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true),opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= date(bo.closed_at) ORDER BY er.as_of DESC LIMIT 1 AND er.as_of <= CAST(bo.closed_at AS DATE) ORDER BY er.as_of DESC NULLS FIRST {{fetch_first(1)}} ) AS eur_rate FROM base_opportunities bo),activities_before_close AS ( SELECT a.opportunity_id, a.opportunity_id AS opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id),opp_final AS ( SELECT owr.id, owr.account_id, owr.owner_user_id, owr.is_won, owr.amount / owr.eur_rate AS amount_eur, EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) / 86400.0 AS days_open, owr.id AS id, owr.account_id AS account_id, owr.owner_user_id AS owner_user_id, owr.is_won AS is_won, CAST(owr.amount / owr.eur_rate AS NUMERIC(18,6)) AS amount_eur, {{seconds_between(owr.created_at, owr.closed_at)}} / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id)SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, round(avg(amount_eur)::numeric, 2) AS avg_amount_eur, round(percentile_cont(0.5) WITHIN GROUP (ORDER BY amount_eur)::numeric, 2) AS median_amount_eur, round(avg(activity_count)::numeric, 2) AS avg_activities_before_close, round(avg(days_open)::numeric, 1) AS avg_days_open, avg(amount_eur) AS avg_amount_eur, {{percentile_cont(0.5, amount_eur)}} AS median_amount_eur, avg(cast(activity_count as numeric(18,6))) AS avg_activities_before_close, avg(days_open) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_repsFROM opp_finalGROUP BY is_wonORDER BY CASE WHEN is_won THEN 0 ELSE 1 ENDORDER BY CASE WHEN is_won THEN 0 ELSE 1 END NULLS LAST
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= CAST(bo.closed_at AS DATE) ORDER BY er.as_of DESC NULLS FIRST FETCH FIRST 1 ROWS ONLY ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id AS opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id AS id, owr.account_id AS account_id, owr.owner_user_id AS owner_user_id, owr.is_won AS is_won, CAST(owr.amount / owr.eur_rate AS NUMERIC(18,6)) AS amount_eur, CAST(EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) AS NUMERIC(18,6)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, avg(amount_eur) AS avg_amount_eur, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount_eur) AS median_amount_eur, avg(cast(activity_count as numeric(18,6))) AS avg_activities_before_close, avg(days_open) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END NULLS LAST
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= CAST(bo.closed_at AS DATE) ORDER BY er.as_of DESC NULLS FIRST LIMIT 1 ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id AS opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id AS id, owr.account_id AS account_id, owr.owner_user_id AS owner_user_id, owr.is_won AS is_won, CAST(owr.amount / owr.eur_rate AS NUMERIC(18,6)) AS amount_eur, CAST(EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) AS NUMERIC(18,6)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, avg(amount_eur) AS avg_amount_eur, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount_eur) AS median_amount_eur, avg(cast(activity_count as numeric(18,6))) AS avg_activities_before_close, avg(days_open) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END NULLS LAST
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= CAST(bo.closed_at AS DATE) ORDER BY er.as_of DESC NULLS FIRST FETCH FIRST 1 ROWS ONLY ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id AS opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id AS id, owr.account_id AS account_id, owr.owner_user_id AS owner_user_id, owr.is_won AS is_won, CAST(owr.amount / owr.eur_rate AS NUMERIC(18,6)) AS amount_eur, CAST(EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) AS NUMERIC(18,6)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, avg(amount_eur) AS avg_amount_eur, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(amount_eur AS DOUBLE PRECISION)) AS median_amount_eur, avg(cast(activity_count as numeric(18,6))) AS avg_activities_before_close, avg(days_open) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END NULLS LAST
WITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= CAST(bo.closed_at AS DATE) ORDER BY er.as_of DESC NULLS FIRST FETCH FIRST 1 ROWS ONLY ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id AS opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id AS id, owr.account_id AS account_id, owr.owner_user_id AS owner_user_id, owr.is_won AS is_won, CAST(owr.amount / owr.eur_rate AS NUMERIC(18,6)) AS amount_eur, CAST(divideDecimal(toDecimal128(dateDiff('microsecond', owr.created_at, owr.closed_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, avg(amount_eur) AS avg_amount_eur, quantileExactInclusive(0.5)(toFloat64(amount_eur)) AS median_amount_eur, avg(cast(activity_count as numeric(18,6))) AS avg_activities_before_close, avg(days_open) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END NULLS LAST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Daniel Kim (analyst for the Head of B2B Commerce): Title: New Business Only: Won vs Lost Opportunity Characteristics Question: Restricted to genuine new-customer acquisition deals (excluding renewals), what reliably differs between won and lost opportunities — deal size, activities before close, days open, or concentration among accounts and reps? Grain: one row per closed outcome (won or lost), New business pipeline only Columns: - outcome (won / lost) - opportunities (count) - average amount in EUR - median amount in EUR - average activities per opportunity before close - average days open (closed_at - created_at) - distinct accounts involved - distinct owning reps (owner_user_id) involved Details: This re-runs last night's won-vs-lost comparison but adds pipeline_id = the tenant's 'New business' pipeline (join pipelines on name = 'New business') to the filter, because the prior report pooled 'New business' (acquisition) and 'Renewals' (existing-customer renew/churn) opportunities together under the same won/lost labels — two very different sales motions with likely different base win rates (the Renewals pipeline's 'Renewed' stage carries a 1.0 default win_probability vs 0.1-0.75 for New business stages), which could distort the comparison. Same tenant scope as before (industry ILIKE '%industrial%' OR '%office equipment%' OR '%wholesal%'), opportunities where is_closed = true, split by is_won. Convert amount to EUR via exchange_rates (base_code='EUR', quote_code = opportunity currency_code, rate as_of closed_at date or nearest prior day). Activities before close = count of activities rows with activities.opportunity_id = opportunities.id and occurred_at <= closed_at, averaged per opportunity within each outcome group. Order 'won' row first, then 'lost'. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= date(bo.closed_at) ORDER BY er.as_of DESC LIMIT 1 ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id, owr.account_id, owr.owner_user_id, owr.is_won, owr.amount / owr.eur_rate AS amount_eur, EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, round(avg(amount_eur)::numeric, 2) AS avg_amount_eur, round(percentile_cont(0.5) WITHIN GROUP (ORDER BY amount_eur)::numeric, 2) AS median_amount_eur, round(avg(activity_count)::numeric, 2) AS avg_activities_before_close, round(avg(days_open)::numeric, 1) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: outcome, opportunities, avg_amount_eur, median_amount_eur, avg_activities_before_close, avg_days_open, distinct_accounts, distinct_reps
submitWITH target_tenants AS ( SELECT id FROM tenants WHERE industry ILIKE '%industrial%' OR industry ILIKE '%office equipment%' OR industry ILIKE '%wholesal%' ), new_business_pipelines AS ( SELECT id, tenant_id FROM pipelines WHERE name = 'New business' ), base_opportunities AS ( SELECT o.* FROM opportunities o JOIN new_business_pipelines p ON o.pipeline_id = p.id AND o.tenant_id = p.tenant_id WHERE o.tenant_id IN (SELECT id FROM target_tenants) AND o.is_closed = true ), opp_with_rate AS ( SELECT bo.*, ( SELECT er.rate FROM exchange_rates er WHERE er.base_code = 'EUR' AND er.quote_code = bo.currency_code AND er.as_of <= date(bo.closed_at) ORDER BY er.as_of DESC LIMIT 1 ) AS eur_rate FROM base_opportunities bo ), activities_before_close AS ( SELECT a.opportunity_id, count(*) AS activity_count FROM activities a JOIN base_opportunities bo ON a.opportunity_id = bo.id AND a.tenant_id = bo.tenant_id WHERE a.occurred_at <= bo.closed_at GROUP BY a.opportunity_id ), opp_final AS ( SELECT owr.id, owr.account_id, owr.owner_user_id, owr.is_won, owr.amount / owr.eur_rate AS amount_eur, EXTRACT(EPOCH FROM (owr.closed_at - owr.created_at)) / 86400.0 AS days_open, COALESCE(ac.activity_count, 0) AS activity_count FROM opp_with_rate owr LEFT JOIN activities_before_close ac ON ac.opportunity_id = owr.id ) SELECT CASE WHEN is_won THEN 'won' ELSE 'lost' END AS outcome, count(*) AS opportunities, round(avg(amount_eur)::numeric, 2) AS avg_amount_eur, round(percentile_cont(0.5) WITHIN GROUP (ORDER BY amount_eur)::numeric, 2) AS median_amount_eur, round(avg(activity_count)::numeric, 2) AS avg_activities_before_close, round(avg(days_open)::numeric, 1) AS avg_days_open, count(DISTINCT account_id) AS distinct_accounts, count(DISTINCT owner_user_id) AS distinct_reps FROM opp_final GROUP BY is_won ORDER BY CASE WHEN is_won THEN 0 ELSE 1 END
Submitted to tonight's batch. Your work on this request is done.