<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: Slow Running SQL Query in Data Engineering</title>
    <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/172479#M56647</link>
    <description>&lt;P&gt;The core reason this query takes 25 minutes is that it scans the 1.4B-row fact table twice and pushes 1.4B wide rows (with 65 additive metrics) through a LEFT JOIN with dim_customer before performing any GROUP BY aggregation.&lt;/P&gt;&lt;P&gt;Here is a breakdown of the main bottlenecks and how to fix them:&lt;/P&gt;&lt;P&gt;1. Double Fact Scan: customers_needing_lookup and final_agg both read fact_profitability_actuals. You can combine these into a single CTE pass by populating a conditional lookup column (CASE WHEN customer_level IS NULL THEN customer END).&lt;BR /&gt;2. Join Before Aggregate: Joining 1.4B rows to customer lookup forces Spark to shuffle high-width rows across nodes. Because SUMs are strictly additive, pre-aggregating the fact table first (fact_pre) reduces billions of rows to a fraction of that volume BEFORE joining dimension tables.&lt;BR /&gt;3. Non-Deterministic Order: ROW_NUMBER() OVER (PARTITION BY customer ORDER BY customer) does not guarantee consistent row selection across runs. Replacing this with a deterministic aggregation (e.g. MAX(customer_level) or filtering by valid_from) eliminates windowing overhead and fixes the correctness risk.&lt;/P&gt;&lt;P&gt;i think the query anuj has suggested would help in reducing the query time.&lt;/P&gt;</description>
    <pubDate>Fri, 09 Oct 2026 15:34:10 GMT</pubDate>
    <dc:creator>Aravind_Reddy</dc:creator>
    <dc:date>2026-10-09T15:34:10Z</dc:date>
    <item>
      <title>Slow Running SQL Query</title>
      <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168563#M55943</link>
      <description>&lt;P&gt;The following Query is running slowly even though it returns only ~ a million rows. It takes around 25 mins. I am trying&amp;nbsp;&lt;SPAN&gt;Produce a pre-aggregated summary table (grouped by 9 dimension attributes) from a large fact table, to be consumed by a Power BI via native query. The goal is one row per (fiscal period, currency type, company, sales org, customer level, product category/package/size/container) combination, with several summed measures — one of which is a&amp;nbsp;SUM()&lt;SPAN&gt;&amp;nbsp;of ~65 individual numeric columns added together, plus a couple of simple subtractions for a profit figure.&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;fact_profitability_actuals&lt;SPAN&gt;&amp;nbsp;— the main fact table, ~1.4B rows in scope after filtering to 2 fiscal years&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;dim_category_mapping&lt;SPAN&gt;&amp;nbsp;— small mapping table (brand → category key)&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;dim_material&lt;SPAN&gt;&amp;nbsp;—&lt;SPAN&gt;&amp;nbsp;&lt;STRONG&gt;~950K rows, used to filter the fact table down to a specific product brand via an inner join on material key&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;dim_customer&lt;SPAN&gt;&amp;nbsp;— ~11M row customer dimension, used only as a&lt;SPAN&gt;&amp;nbsp;&lt;STRONG&gt;fallback lookup: a small percentage of fact rows are missing a "customer level" attribute directly, so I left-join this dimension (filtered down to just the customers that actually need it via a semi-join first) to backfill it via&lt;SPAN&gt;&amp;nbsp;COALESCE&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Joins/conditions&lt;/STRONG&gt;&lt;/LI&gt;&lt;LI&gt;INNER JOIN&lt;SPAN&gt;&amp;nbsp;fact → material dimension (on material key) — filters fact rows to the target brand.&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;LEFT JOIN&lt;SPAN&gt;&amp;nbsp;fact → customer dimension (on customer key, only when the fact-level attribute is&lt;SPAN&gt;&amp;nbsp;NULL) — backfills a missing attribute, doesn't filter rows.&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;WHERE&lt;SPAN&gt;&amp;nbsp;filter on fiscal period range (2 years), confirmed to prune partitions correctly.&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;Final&lt;SPAN&gt;&lt;SPAN&gt;&amp;nbsp;GROUP BY&lt;SPAN&gt;&amp;nbsp;on 9 columns, aggregating volume, a 65-column additive&lt;SPAN&gt;&amp;nbsp;SUM, and a profit calc (SUM(a) - SUM(b) - SUM(c)).&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;LI-CODE lang="c"&gt;&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;```SQL&lt;/P&gt;&lt;P&gt;WITH brand_categories AS (&lt;BR /&gt;SELECT category_brand_key&lt;BR /&gt;FROM catalog_prod.schema_a.dim_brand_category_mappings&lt;BR /&gt;WHERE brand_group = 'Brand X'&lt;BR /&gt;GROUP BY category_brand_key&lt;BR /&gt;),&lt;/P&gt;&lt;P&gt;brand_materials AS (&lt;BR /&gt;SELECT&lt;BR /&gt;m.material,&lt;BR /&gt;m.category_desc,&lt;BR /&gt;m.package_desc,&lt;BR /&gt;m.pack_size,&lt;BR /&gt;m.container_desc&lt;BR /&gt;FROM catalog_prod.schema_b.dim_material m&lt;BR /&gt;INNER JOIN brand_categories cat&lt;BR /&gt;ON cat.category_brand_key = m.category_key&lt;BR /&gt;),&lt;/P&gt;&lt;P&gt;-- customers that actually need a level lookup (fact value is null) within our date range&lt;BR /&gt;customers_needing_lookup AS (&lt;BR /&gt;SELECT DISTINCT f.customer&lt;BR /&gt;FROM catalog_prod.schema_c.fact_profitability_actuals f&lt;BR /&gt;WHERE f.customer_level IS NULL&lt;BR /&gt;AND f.customer IS NOT NULL&lt;BR /&gt;AND f.fiscper &amp;gt;= CONCAT(CAST(YEAR(CURRENT_DATE()) - 1 AS STRING), '001')&lt;BR /&gt;AND f.fiscper &amp;lt; CONCAT(CAST(YEAR(CURRENT_DATE()) + 1 AS STRING), '001')&lt;BR /&gt;),&lt;/P&gt;&lt;P&gt;-- shrunk lookup: only the customers we need, one row per customer (guards against SCD/history fan-out)&lt;BR /&gt;customer_lookup AS (&lt;BR /&gt;SELECT customer, customer_level&lt;BR /&gt;FROM (&lt;BR /&gt;SELECT&lt;BR /&gt;c.customer,&lt;BR /&gt;c.customer_level,&lt;BR /&gt;ROW_NUMBER() OVER (PARTITION BY c.customer ORDER BY c.customer) AS rn&lt;BR /&gt;FROM catalog_prod.schema_b.dim_customer c&lt;BR /&gt;INNER JOIN customers_needing_lookup n&lt;BR /&gt;ON n.customer = c.customer&lt;BR /&gt;WHERE c.customer_level IS NOT NULL&lt;BR /&gt;) t&lt;BR /&gt;WHERE rn = 1&lt;BR /&gt;),&lt;/P&gt;&lt;P&gt;final_agg AS (&lt;BR /&gt;SELECT&lt;BR /&gt;f.fiscper,&lt;BR /&gt;f.curr_type,&lt;BR /&gt;f.comp_code,&lt;BR /&gt;f.salesorg,&lt;BR /&gt;COALESCE(f.customer_level, c.customer_level) AS customer_level,&lt;BR /&gt;m.category_desc,&lt;BR /&gt;m.package_desc,&lt;BR /&gt;m.pack_size,&lt;BR /&gt;m.container_desc,&lt;/P&gt;&lt;P&gt;SUM(f.volume_in_uc) AS volume_in_uc,&lt;/P&gt;&lt;P&gt;SUM(&lt;BR /&gt;COALESCE(f.metric_001, 0)&lt;BR /&gt;+ COALESCE(f.metric_002, 0)&lt;BR /&gt;+ COALESCE(f.metric_003, 0)&lt;BR /&gt;+ COALESCE(f.metric_006, 0)&lt;BR /&gt;+ COALESCE(f.metric_007, 0)&lt;BR /&gt;+ COALESCE(f.metric_008, 0)&lt;BR /&gt;+ COALESCE(f.metric_009, 0)&lt;BR /&gt;+ COALESCE(f.metric_010, 0)&lt;BR /&gt;+ COALESCE(f.metric_011, 0)&lt;BR /&gt;+ COALESCE(f.metric_016, 0)&lt;BR /&gt;+ COALESCE(f.metric_029, 0)&lt;BR /&gt;+ COALESCE(f.metric_051, 0)&lt;BR /&gt;+ COALESCE(f.metric_053, 0)&lt;BR /&gt;+ COALESCE(f.metric_004, 0)&lt;BR /&gt;+ COALESCE(f.metric_005, 0)&lt;BR /&gt;+ COALESCE(f.metric_012, 0)&lt;BR /&gt;+ COALESCE(f.metric_018, 0)&lt;BR /&gt;+ COALESCE(f.metric_041, 0)&lt;BR /&gt;+ COALESCE(f.metric_023, 0)&lt;BR /&gt;+ COALESCE(f.metric_024, 0)&lt;BR /&gt;+ COALESCE(f.metric_025, 0)&lt;BR /&gt;+ COALESCE(f.metric_026, 0)&lt;BR /&gt;+ COALESCE(f.metric_028, 0)&lt;BR /&gt;+ COALESCE(f.metric_014, 0)&lt;BR /&gt;+ COALESCE(f.metric_021, 0)&lt;BR /&gt;+ COALESCE(f.metric_022, 0)&lt;BR /&gt;+ COALESCE(f.metric_013, 0)&lt;BR /&gt;+ COALESCE(f.metric_017, 0)&lt;BR /&gt;+ COALESCE(f.metric_019, 0)&lt;BR /&gt;+ COALESCE(f.metric_020, 0)&lt;BR /&gt;+ COALESCE(f.metric_027, 0)&lt;BR /&gt;+ COALESCE(f.metric_015, 0)&lt;BR /&gt;+ COALESCE(f.metric_030, 0)&lt;BR /&gt;+ COALESCE(f.metric_034, 0)&lt;BR /&gt;+ COALESCE(f.metric_035, 0)&lt;BR /&gt;+ COALESCE(f.metric_036, 0)&lt;BR /&gt;+ COALESCE(f.metric_037, 0)&lt;BR /&gt;+ COALESCE(f.metric_038, 0)&lt;BR /&gt;+ COALESCE(f.metric_039, 0)&lt;BR /&gt;+ COALESCE(f.metric_040, 0)&lt;BR /&gt;+ COALESCE(f.metric_060, 0)&lt;BR /&gt;+ COALESCE(f.metric_062, 0)&lt;BR /&gt;+ COALESCE(f.metric_063, 0)&lt;BR /&gt;+ COALESCE(f.metric_064, 0)&lt;BR /&gt;+ COALESCE(f.metric_112, 0)&lt;BR /&gt;+ COALESCE(f.metric_056, 0)&lt;BR /&gt;+ COALESCE(f.metric_044, 0)&lt;BR /&gt;+ COALESCE(f.metric_031, 0)&lt;BR /&gt;+ COALESCE(f.metric_032, 0)&lt;BR /&gt;+ COALESCE(f.metric_033, 0)&lt;BR /&gt;+ COALESCE(f.metric_042, 0)&lt;BR /&gt;+ COALESCE(f.metric_048, 0)&lt;BR /&gt;+ COALESCE(f.metric_049, 0)&lt;BR /&gt;+ COALESCE(f.metric_057, 0)&lt;BR /&gt;+ COALESCE(f.metric_058, 0)&lt;BR /&gt;+ COALESCE(f.metric_061, 0)&lt;BR /&gt;+ COALESCE(f.metric_111, 0)&lt;BR /&gt;+ COALESCE(f.metric_113, 0)&lt;BR /&gt;+ COALESCE(f.metric_043, 0)&lt;BR /&gt;+ COALESCE(f.metric_045, 0)&lt;BR /&gt;+ COALESCE(f.metric_046, 0)&lt;BR /&gt;+ COALESCE(f.metric_050, 0)&lt;BR /&gt;+ COALESCE(f.metric_055, 0)&lt;BR /&gt;+ COALESCE(f.metric_054, 0)&lt;BR /&gt;+ COALESCE(f.metric_065, 0)&lt;BR /&gt;) AS nnr,&lt;/P&gt;&lt;P&gt;SUM(COALESCE(f.gross_profit_total, 0))&lt;BR /&gt;- SUM(COALESCE(f.gross_profit_other, 0))&lt;BR /&gt;- SUM(COALESCE(f.net_effect_of_ic_sales_purchases, 0)) AS gp,&lt;BR /&gt;SUM(COALESCE(f.net_sales_revenue, 0)) AS NSR&lt;/P&gt;&lt;P&gt;FROM catalog_prod.schema_c.fact_profitability_actuals f&lt;/P&gt;&lt;P&gt;INNER JOIN brand_materials m&lt;BR /&gt;ON f.material = m.material&lt;/P&gt;&lt;P&gt;LEFT JOIN customer_lookup c&lt;BR /&gt;ON f.customer = c.customer&lt;BR /&gt;AND f.customer_level IS NULL&lt;/P&gt;&lt;P&gt;WHERE f.fiscper &amp;gt;= CONCAT(CAST(YEAR(CURRENT_DATE()) - 1 AS STRING), '001')&lt;BR /&gt;AND f.fiscper &amp;lt; CONCAT(CAST(YEAR(CURRENT_DATE()) + 1 AS STRING), '001')&lt;/P&gt;&lt;P&gt;GROUP BY&lt;BR /&gt;f.fiscper, f.curr_type, f.comp_code, f.salesorg,&lt;BR /&gt;m.category_desc, m.package_desc, m.pack_size, m.container_desc,&lt;BR /&gt;COALESCE(f.customer_level, c.customer_level)&lt;BR /&gt;)&lt;/P&gt;&lt;P&gt;SELECT&lt;BR /&gt;fiscper, curr_type, comp_code, salesorg,&lt;BR /&gt;category_desc, package_desc, pack_size, container_desc, customer_level,&lt;BR /&gt;volume_in_uc,&lt;BR /&gt;nnr,&lt;BR /&gt;nnr / NULLIF(volume_in_uc, 0) AS `NNR/UC`,&lt;BR /&gt;gp / NULLIF(volume_in_uc, 0) AS `GP/UC`,&lt;BR /&gt;gp AS `GP`,&lt;BR /&gt;NSR&lt;BR /&gt;FROM final_agg&lt;/P&gt;&lt;P&gt;```&lt;/P&gt;</description>
      <pubDate>Mon, 14 Sep 2026 18:19:17 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168563#M55943</guid>
      <dc:creator>Sherbo</dc:creator>
      <dc:date>2026-09-14T18:19:17Z</dc:date>
    </item>
    <item>
      <title>Re: Slow Running SQL Query</title>
      <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168636#M55957</link>
      <description>&lt;P&gt;Double scan of the fact table — customers_needing_lookup and final_agg both scan fact_profitability_actuals with different predicates, so Spark can't reuse the plan. Fix: precompute a small deduped (customer, customer_level) lookup table once (refreshed on dim_customer changes) instead of deriving it live each run. Removes one full pass over the 1.4B-row table.&lt;/P&gt;</description>
      <pubDate>Tue, 15 Sep 2026 10:45:34 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168636#M55957</guid>
      <dc:creator>aayush_410</dc:creator>
      <dc:date>2026-09-15T10:45:34Z</dc:date>
    </item>
    <item>
      <title>Re: Slow Running SQL Query</title>
      <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168637#M55958</link>
      <description>&lt;P&gt;With 1.4B rows, I’d first check join cardinality and whether filtering happens before the joins. Early reduction of the fact table could make a huge difference, especially before the final GROUP BY.&lt;/P&gt;</description>
      <pubDate>Tue, 15 Sep 2026 10:52:31 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/168637#M55958</guid>
      <dc:creator>ThiamLee</dc:creator>
      <dc:date>2026-09-15T10:52:31Z</dc:date>
    </item>
    <item>
      <title>Re: Slow Running SQL Query</title>
      <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/172474#M56646</link>
      <description>&lt;DIV&gt;
&lt;DIV&gt;
&lt;DIV&gt;
&lt;P&gt;&lt;STRONG&gt;Short version:&lt;/STRONG&gt; the SQL is logically fine. The cost comes from scanning the 1.4B-row fact table twice and pushing every fact row through the customer left join before any aggregation happens. Aggregate first, join the small result afterwards, and the expensive part mostly goes away.&lt;/P&gt;
&lt;H2&gt;What's making it slow&lt;/H2&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;STRONG&gt;Two scans of the fact table.&lt;/STRONG&gt; &lt;CODE&gt;customers_needing_lookup&lt;/CODE&gt; reads the fact table just to find the NULL-level customers, then the main query reads it again.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;The left join runs on 1.4B rows.&lt;/STRONG&gt; The &lt;CODE&gt;AND f.customer_level IS NULL&lt;/CODE&gt; in the &lt;CODE&gt;ON&lt;/CODE&gt; clause doesn't stop the other ~99% of rows from going through the join. If &lt;CODE&gt;customer_lookup&lt;/CODE&gt; is too big to broadcast, that's a sort-merge join that shuffles the 65-column-wide fact rows. I'd bet this is your dominant cost. Check Query Profile for a large shuffle or spill on that join.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Brand filter via join only.&lt;/STRONG&gt; The material join shrinks the data, but unless the fact table's file layout lines up with &lt;CODE&gt;material&lt;/CODE&gt;, you still read most files in the date range before the join discards rows.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Non-deterministic dedupe.&lt;/STRONG&gt; &lt;CODE&gt;ROW_NUMBER() ... ORDER BY c.customer&lt;/CODE&gt; partitions by &lt;CODE&gt;customer&lt;/CODE&gt; and orders by the same column, so which row wins for a customer with multiple versions is arbitrary. That's a correctness risk, not just a perf one.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2&gt;Rewrite: aggregate before you join&lt;/H2&gt;
&lt;P&gt;SUMs are additive, so you can pre-aggregate the fact table down to a much smaller grain, then join the dimensions and re-aggregate. Only carry &lt;CODE&gt;customer&lt;/CODE&gt; for rows that actually need a lookup:&lt;CODE class="language-sql"&gt;
&lt;/CODE&gt;&lt;/P&gt;
&lt;/DIV&gt;
&lt;H2&gt;Full query&lt;/H2&gt;
&lt;PRE&gt;&lt;CODE class="language-sql"&gt;WITH brand_categories AS (
  SELECT DISTINCT category_brand_key
  FROM catalog_prod.schema_a.dim_brand_category_mappings
  WHERE brand_group = 'Brand X'
),

brand_materials AS (
  SELECT m.material, m.category_desc, m.package_desc, m.pack_size, m.container_desc
  FROM catalog_prod.schema_b.dim_material m
  JOIN brand_categories cat
    ON cat.category_brand_key = m.category_key
),

-- Single scan of the fact table, aggregated BEFORE any dimension join.
-- lookup_customer is populated only for rows that need the level backfill,
-- so the other ~99% of rows collapse to a handful of rows per material.
fact_pre AS (
  SELECT
    f.fiscper,
    f.curr_type,
    f.comp_code,
    f.salesorg,
    f.material,
    f.customer_level,
    CASE WHEN f.customer_level IS NULL THEN f.customer END AS lookup_customer,

    SUM(f.volume_in_uc) AS volume_in_uc,

    SUM(
        COALESCE(f.metric_001, 0) + COALESCE(f.metric_002, 0) + COALESCE(f.metric_003, 0)
      + COALESCE(f.metric_006, 0) + COALESCE(f.metric_007, 0) + COALESCE(f.metric_008, 0)
      + COALESCE(f.metric_009, 0) + COALESCE(f.metric_010, 0) + COALESCE(f.metric_011, 0)
      + COALESCE(f.metric_016, 0) + COALESCE(f.metric_029, 0) + COALESCE(f.metric_051, 0)
      + COALESCE(f.metric_053, 0) + COALESCE(f.metric_004, 0) + COALESCE(f.metric_005, 0)
      + COALESCE(f.metric_012, 0) + COALESCE(f.metric_018, 0) + COALESCE(f.metric_041, 0)
      + COALESCE(f.metric_023, 0) + COALESCE(f.metric_024, 0) + COALESCE(f.metric_025, 0)
      + COALESCE(f.metric_026, 0) + COALESCE(f.metric_028, 0) + COALESCE(f.metric_014, 0)
      + COALESCE(f.metric_021, 0) + COALESCE(f.metric_022, 0) + COALESCE(f.metric_013, 0)
      + COALESCE(f.metric_017, 0) + COALESCE(f.metric_019, 0) + COALESCE(f.metric_020, 0)
      + COALESCE(f.metric_027, 0) + COALESCE(f.metric_015, 0) + COALESCE(f.metric_030, 0)
      + COALESCE(f.metric_034, 0) + COALESCE(f.metric_035, 0) + COALESCE(f.metric_036, 0)
      + COALESCE(f.metric_037, 0) + COALESCE(f.metric_038, 0) + COALESCE(f.metric_039, 0)
      + COALESCE(f.metric_040, 0) + COALESCE(f.metric_060, 0) + COALESCE(f.metric_062, 0)
      + COALESCE(f.metric_063, 0) + COALESCE(f.metric_064, 0) + COALESCE(f.metric_112, 0)
      + COALESCE(f.metric_056, 0) + COALESCE(f.metric_044, 0) + COALESCE(f.metric_031, 0)
      + COALESCE(f.metric_032, 0) + COALESCE(f.metric_033, 0) + COALESCE(f.metric_042, 0)
      + COALESCE(f.metric_048, 0) + COALESCE(f.metric_049, 0) + COALESCE(f.metric_057, 0)
      + COALESCE(f.metric_058, 0) + COALESCE(f.metric_061, 0) + COALESCE(f.metric_111, 0)
      + COALESCE(f.metric_113, 0) + COALESCE(f.metric_043, 0) + COALESCE(f.metric_045, 0)
      + COALESCE(f.metric_046, 0) + COALESCE(f.metric_050, 0) + COALESCE(f.metric_055, 0)
      + COALESCE(f.metric_054, 0) + COALESCE(f.metric_065, 0)
    ) AS nnr,

    SUM(COALESCE(f.gross_profit_total, 0))
      - SUM(COALESCE(f.gross_profit_other, 0))
      - SUM(COALESCE(f.net_effect_of_ic_sales_purchases, 0)) AS gp,

    SUM(COALESCE(f.net_sales_revenue, 0)) AS nsr

  FROM catalog_prod.schema_c.fact_profitability_actuals f
  WHERE f.fiscper &amp;gt;= CONCAT(CAST(YEAR(CURRENT_DATE()) - 1 AS STRING), '001')
    AND f.fiscper &amp;lt;  CONCAT(CAST(YEAR(CURRENT_DATE()) + 1 AS STRING), '001')
    AND f.material IN (SELECT material FROM brand_materials)
  GROUP BY
    f.fiscper, f.curr_type, f.comp_code, f.salesorg, f.material,
    f.customer_level,
    CASE WHEN f.customer_level IS NULL THEN f.customer END
),

-- One deterministic row per customer. Replace MAX() with your real
-- tie-breaker (e.g. latest valid_from) if levels can differ across versions.
customer_lookup AS (
  SELECT customer, MAX(customer_level) AS customer_level
  FROM catalog_prod.schema_b.dim_customer
  WHERE customer_level IS NOT NULL
  GROUP BY customer
),

final_agg AS (
  SELECT
    p.fiscper,
    p.curr_type,
    p.comp_code,
    p.salesorg,
    COALESCE(p.customer_level, c.customer_level) AS customer_level,
    m.category_desc,
    m.package_desc,
    m.pack_size,
    m.container_desc,
    SUM(p.volume_in_uc) AS volume_in_uc,
    SUM(p.nnr)          AS nnr,
    SUM(p.gp)           AS gp,
    SUM(p.nsr)          AS nsr
  FROM fact_pre p
  JOIN brand_materials m
    ON p.material = m.material
  LEFT JOIN customer_lookup c
    ON p.lookup_customer = c.customer
  GROUP BY
    p.fiscper, p.curr_type, p.comp_code, p.salesorg,
    COALESCE(p.customer_level, c.customer_level),
    m.category_desc, m.package_desc, m.pack_size, m.container_desc
)

SELECT
  fiscper, curr_type, comp_code, salesorg,
  category_desc, package_desc, pack_size, container_desc, customer_level,
  volume_in_uc,
  nnr,
  nnr / NULLIF(volume_in_uc, 0) AS `NNR/UC`,
  gp  / NULLIF(volume_in_uc, 0) AS `GP/UC`,
  gp  AS `GP`,
  nsr AS `NSR`
FROM final_agg;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;H2&gt;Why this shape is faster&lt;/H2&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG&gt;One fact scan instead of two.&lt;/STRONG&gt; The &lt;CODE&gt;customers_needing_lookup&lt;/CODE&gt; CTE is gone. The NULL-level customers fall out of the same pass via &lt;CODE&gt;lookup_customer&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;The join now happens after aggregation.&lt;/STRONG&gt; Dimension joins run against the pre-aggregated &lt;CODE&gt;fact_pre&lt;/CODE&gt; instead of 1.4B wide rows, so the big shuffle on the customer join disappears.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Re-aggregation is safe.&lt;/STRONG&gt; Every measure is a plain SUM. The ratios (&lt;CODE&gt;NNR/UC&lt;/CODE&gt;, &lt;CODE&gt;GP/UC&lt;/CODE&gt;) are computed only in the last SELECT, from the summed totals, so&lt;/LI&gt;
&lt;/UL&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;CODE class="language-sql"&gt;
&lt;/CODE&gt;&lt;/P&gt;
&lt;/DIV&gt;</description>
      <pubDate>Fri, 09 Oct 2026 14:32:15 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/172474#M56646</guid>
      <dc:creator>anuj_lathi</dc:creator>
      <dc:date>2026-10-09T14:32:15Z</dc:date>
    </item>
    <item>
      <title>Re: Slow Running SQL Query</title>
      <link>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/172479#M56647</link>
      <description>&lt;P&gt;The core reason this query takes 25 minutes is that it scans the 1.4B-row fact table twice and pushes 1.4B wide rows (with 65 additive metrics) through a LEFT JOIN with dim_customer before performing any GROUP BY aggregation.&lt;/P&gt;&lt;P&gt;Here is a breakdown of the main bottlenecks and how to fix them:&lt;/P&gt;&lt;P&gt;1. Double Fact Scan: customers_needing_lookup and final_agg both read fact_profitability_actuals. You can combine these into a single CTE pass by populating a conditional lookup column (CASE WHEN customer_level IS NULL THEN customer END).&lt;BR /&gt;2. Join Before Aggregate: Joining 1.4B rows to customer lookup forces Spark to shuffle high-width rows across nodes. Because SUMs are strictly additive, pre-aggregating the fact table first (fact_pre) reduces billions of rows to a fraction of that volume BEFORE joining dimension tables.&lt;BR /&gt;3. Non-Deterministic Order: ROW_NUMBER() OVER (PARTITION BY customer ORDER BY customer) does not guarantee consistent row selection across runs. Replacing this with a deterministic aggregation (e.g. MAX(customer_level) or filtering by valid_from) eliminates windowing overhead and fixes the correctness risk.&lt;/P&gt;&lt;P&gt;i think the query anuj has suggested would help in reducing the query time.&lt;/P&gt;</description>
      <pubDate>Fri, 09 Oct 2026 15:34:10 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/slow-running-sql-query/m-p/172479#M56647</guid>
      <dc:creator>Aravind_Reddy</dc:creator>
      <dc:date>2026-10-09T15:34:10Z</dc:date>
    </item>
  </channel>
</rss>

