Ravindra BagaleCourses & study guides मराठी Track your progress

4. Connecting to Databases: SQL Server, MySQL, DirectQuery and Live Connection

4.12 SQL for Power BI: Views, Joins, CTEs and Where to Transform

Start with a problem

Amir's team argues: should cleaning and totals happen in SQL or in Power BI? Someone created a view that groups by city, and now nobody can split Pune by store.

Explain simply

Use SQL for shared, heavy, row-level rules close to the data (views). Use Power Query for file sources and report-specific steps. Use DAX for totals that must react to filters. Keep the grain you may need later.

Show the data

order_id order_date store_id platform category amount delivery_mins status
301 2026-04-06 S01 Blinkit Dairy 400 8 Delivered
302 2026-04-06 S02 Amazon Now Snacks 200 12 Delivered
303 2026-04-07 S03 Blinkit Dairy 300 14 Delivered
304 2026-05-04 S04 Blinkit Fruits 150 9 Delivered
305 2026-05-04 S05 Amazon Now Snacks 450 7 Delivered
306 2026-05-05 S01 Blinkit Fruits 250 16 Delivered
307 2026-05-05 S04 Amazon Now Dairy 350 11 Delivered
308 2026-05-05 S05 Blinkit Dairy 0 NULL Cancelled

Fictional sample data. Practice file: quickcommerce_practice.sql (source: src/practice/).

Demonstrate

  1. Create a view at order grain: CREATE VIEW dbo.vw_orders_clean AS SELECT order_id, order_date, store_id, amount, status FROM dbo.orders WHERE amount IS NOT NULL;

    Your result should look like this:

    vw_orders_clean
    8 rows (one per order)
  2. Compare a grouped view: SELECT s.city, SUM(o.amount) AS sales FROM dbo.orders o JOIN dbo.dark_store s ON s.store_id = o.store_id GROUP BY s.city;

    Your result should look like this:

    city sales
    Kolhapur 450
    Nashik 800
    Pune 850
  3. Import vw_orders_clean and dark_store; measure Total Sales = SUM(vw_orders_clean[amount]); matrix City › Store.

    Your result should look like this:

    Pune Kothrud 650 · Baner 200
  4. Running total in SQL (window function): SUM(SUM(amount)) OVER (ORDER BY order_date) grouped by date.

    Your result should look like this:

    order_date sales running
    2026-04-06 600 600
    2026-04-07 300 900
    2026-05-04 600 1,500
    2026-05-05 600 2,100

Show the result

Your result should look like this (fictional sample):

Approach Can split Pune by store? Reacts to slicers?
Grouped view (city level) No Only by city
Order-grain view + DAX Yes (Kothrud 650, Baner 200) Yes
SQL running total column — No (fixed at load time)

Explain what happened

The grouped view threw away order and store detail, so later questions could not be answered. The order-grain view keeps every option open, and DAX totals respond to filters. A SQL running total is fixed when loaded; a DAX running total respects slicers.

Explore further

  • CTEs and stored procedures can be used through native SQL, but they usually stop folding; prefer views.

Detailed reference for this lesson:

SQL and Power Query can both prepare data. The real question is where each piece of work should happen.

Use SQL when the database already knows the business rule, the table is large, or several reports must share one definition. Use Power Query when the source is a file, the rule is only for this report, or you cannot create objects in the database.

Joins. A star schema in SQL is the same shape as Module 11. orders joins to product on product_id and to store on store_id. In Power BI you usually do not join them into one flat query. Load the three tables and relate them in the model. A SQL join that flattens everything is how you get a wide, repeating table and a slow model.

Aggregations in SQL. GROUP BY city is useful for a small summary table. If you group away the order id, you cannot count distinct orders later in a new way. Prefer to import the grain you might still filter, and aggregate with DAX measures.

Views. A view is a saved SELECT. Power BI treats it like a table. Good uses: hide unwanted columns, fix a data type, apply a stable filter the business agreed (for example delivered rows only, if that is really the rule). The view stays under the database owner's control.

CTEs (WITH ... AS). A CTE is a named step inside one statement. Power BI does not "call" a CTE by name. You either put the whole statement in a native query (folding usually stops) or, better, save the logic as a view.

Window functions such as SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date) compute a running total inside the database. That is valid SQL. In Power BI, a running total is usually a measure (Module 13.16), so it still reacts to slicers. A window column is frozen at refresh. Use a window function in a view when the number must be stored per row (for example the previous order date on that row). Use a measure when the user will filter the page.

Stored procedures. Desktop can run some procedures through native SQL (EXEC ...), depending on the source. They often do not fold, they are awkward in DirectQuery, and the Service refresh account must be allowed to execute them. Prefer a view or a table-valued function the connector can read as a table. If a procedure is the only door the DBA gives you, load its result with Import and keep the procedure simple.

Worked example, fictional rows:

order_id city amount
B-101 Pune 450
B-102 Pune 800
B-103 Kolhapur 300
-- View the model can import. Grain stays one row per order.
CREATE VIEW dbo.vw_orders_clean AS
SELECT order_id, city, amount, order_date
FROM dbo.orders
WHERE amount IS NOT NULL;

A second view vw_orders_by_city with GROUP BY city would show Pune 1250 and Kolhapur 300, and you could never split Pune by product later. Import vw_orders_clean. Write [Total Sales] = SUM(Orders[amount]) in DAX.

Ravindra Bagale's Tip

In interviews many students say "do everything in SQL, Power BI is only charts". The opposite is also wrong: doing everything in DAX and never using GROUP BY in the database. Heavy, shared, row-level cleaning goes in a view; filters the user clicks stay as measures. Remember the order.

Fix common mistakes

Wrong result Reason Correction
"Do everything in SQL, Power BI is just charts" Ignores filter-aware DAX Rows and shared rules in SQL; filter-aware totals in DAX
Cannot drill Pune into stores Imported a city-level aggregate Import the order grain
Running total ignores the Platform slicer Pre-computed in SQL Write it as a DAX measure

Practise independently

Practice task

Using the order-grain model, what is the running total by date for Blinkit only?

Hints
  • Blinkit by date: 06-Apr 400, 07-Apr 300, 04-May 150, 05-May 250 + 0.
Solution

06-Apr ₹400; 07-Apr ₹700; 04-May ₹850; 05-May ₹1,100.

Think like a professional

Ask "which questions might come next?" before aggregating in SQL. Keep the finest grain that the business could ask about.

Thodkyaat sangaycha tar (quick recap)

Type the server and database names exactly, bring only the tables you need, check folding, and remember the difference between DirectQuery and Live connection. Do not forget: scheduled refresh does not work without a gateway for on-premises data. Now let us move on to web data.

Ravi Sir's Cards for this lesson

मराठी cards