SQL refresher — DE-এর জন্য
এই পাঠে যা শিখবেন
- SELECT, WHERE, GROUP BY, HAVING — সঠিক ক্রম ও semantic
- JOIN-এর ৪ ধরন — set-theoretic ভাবনা ও bug-prone case
- CTE ও subquery — কখন কোনটি, readability ও performance trade-off
- EXPLAIN ANALYZE পড়া ও index-এর প্রভাব বোঝা
১ · কেন DE-তে SQL এত গুরুত্বপূর্ণ
একজন data engineer প্রতিদিন গড়ে ৫০-১০০ লাইন SQL লেখেন — pipeline-এ data transformation, dbt model, Airflow task, ad-hoc debugging। আধুনিক tooling SQL-কে আরও কেন্দ্রে এনেছে: Spark SQL, BigQuery SQL, dbt, ksqlDB, Materialize — সবগুলোতে SQL সর্বোচ্চ-level interface। Python ভালো, কিন্তু ৭০-৮০% production transformation শেষমেশ SQL।
এই পাঠ — Window function ছাড়া (পরের পাঠে) — DE-র core SQL ক্ষমতা refresh করবে।
লেখা: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
চলে: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
এই কারণে WHERE-এ alias ব্যবহার করা যায় না (তখনো SELECT চলে নি), কিন্তু HAVING ও ORDER BY-তে যায়।
২ · SELECT, WHERE, GROUP BY — সঠিক বুনন
একটি বাস্তব Daraz query — division-অনুযায়ী মাসিক revenue:
SELECT c.division,
DATE_TRUNC('month', o.order_ts) AS order_month,
COUNT(*) AS orders,
SUM(o.revenue_bdt) AS total_revenue,
AVG(o.revenue_bdt) AS avg_order_value
FROM fact_orders o
JOIN dim_customer c USING (customer_key)
WHERE o.order_ts >= '2025-01-01'
AND o.order_ts < '2025-04-01'
AND o.status = 'delivered'
GROUP BY c.division, DATE_TRUNC('month', o.order_ts)
HAVING SUM(o.revenue_bdt) > 1000000 -- ১০ লাখের বেশি
ORDER BY order_month, total_revenue DESC;
WHERE), aggregate (GROUP BY), aggregate-এর উপর filter (HAVING), শেষে sort। এই ক্রম mastery — DE-র দৈনন্দিন কাজের ৪০%।
SELECT-এ এমন column লিখলেন যা GROUP BY-তে নেই — Postgres error দেবে, কিন্তু MySQL silent ভুল answer দিতে পারে। সব non-aggregate column অবশ্যই GROUP BY-তে থাকতে হবে।
৩ · JOIN — ৪ প্রকার ও set-theoretic ভাবনা
Two table A, B। JOIN-এর ফলাফল কোন rows আসবে:
- INNER JOIN: দু'টোতেই matched key — A ∩ B।
- LEFT JOIN: A-র সব row + B থেকে matched (না হলে NULL)।
- RIGHT JOIN: উল্টো — B-র সব row। Practice-এ rare; LEFT-এ rewrite।
- FULL OUTER JOIN: দু'দিকে সব row — match হলে পাশাপাশি, না হলে NULL।
-- gold-tier customer-দের মধ্যে যাদের কোনো order নেই (LEFT JOIN + IS NULL)
SELECT c.customer_id, c.name, c.signup_date
FROM dim_customer c
LEFT JOIN fact_orders o
ON c.customer_key = o.customer_key
AND o.order_ts >= CURRENT_DATE - INTERVAL '90 days'
WHERE c.customer_tier = 'gold'
AND o.order_id IS NULL; -- key trick: anti-join via LEFT + NULL
-- Daraz-এ যেসব product বিক্রি কখনো হয়নি (anti-join)
SELECT p.product_id, p.name
FROM dim_product p
LEFT JOIN fact_orders o USING (product_key)
WHERE o.product_key IS NULL;
LEFT JOIN ... WHERE right.col IS NULL = "A-তে আছে কিন্তু B-তে নেই" — anti-join। DE-তে অসংখ্যবার লাগে: missing record খোঁজা, data quality check।
৪ · JOIN-এর সাধারণ bug — fan-out
সবচেয়ে subtle DE bug — fan-outFan-outJOIN-এর পর row সংখ্যা প্রত্যাশার চেয়ে অনেক বেড়ে যাওয়া — কারণ "one" দিকে duplicate বা multi-match row। SUM/AVG ভুল হয়।। মনে করুন, fact_orders-এ ১ লাখ order, fact_returns-এ ১০ হাজার return। JOIN করলে আশা ১ লাখ। কিন্তু এক order-এ যদি multiple return থাকে — row বেড়ে গেল, এবং SUM(revenue_bdt) double-counted।
-- ভুল: fan-out — return row বেশি হলে revenue double-counted
SELECT o.customer_key, SUM(o.revenue_bdt) AS revenue, COUNT(r.return_id) AS returns
FROM fact_orders o
LEFT JOIN fact_returns r ON o.order_id = r.order_id
GROUP BY o.customer_key;
-- ভুল answer!
-- সঠিক: aggregate আগে, JOIN পরে
SELECT o.customer_key,
SUM(o.revenue_bdt) AS revenue,
COALESCE(r.return_count, 0) AS returns
FROM fact_orders o
LEFT JOIN (
SELECT order_id, COUNT(*) AS return_count
FROM fact_returns
GROUP BY order_id
) r ON o.order_id = r.order_id
GROUP BY o.customer_key, r.return_count;
৫ · CTE (WITH) — readable, modular SQL
CTECommon Table ExpressionWITH clause দিয়ে এক query-র মধ্যে temporary "named subquery" তৈরি — পাঠযোগ্যতা বহুগুণ বাড়ায়। modern SQL-এ chain করা যায়। = WITH ব্যবহার করে নাম দেওয়া step। জটিল query-কে ছোট ছোট পদে ভেঙে — প্রতিটি একটি পরিষ্কার "চিন্তা"।
-- bKash: গত ৩০ দিনে এমন গ্রাহক যাদের গড় daily transaction ১০০০-এর বেশি
WITH daily_tx AS (
SELECT customer_key,
DATE(transaction_ts) AS tx_date,
SUM(amount_bdt) AS daily_amount
FROM fact_transaction
WHERE transaction_ts >= CURRENT_DATE - INTERVAL '30 days'
AND status = 'success'
GROUP BY customer_key, DATE(transaction_ts)
),
customer_avg AS (
SELECT customer_key,
AVG(daily_amount) AS avg_daily_bdt,
COUNT(*) AS active_days
FROM daily_tx
GROUP BY customer_key
HAVING AVG(daily_amount) > 1000
)
SELECT c.msisdn, c.customer_tier, ca.avg_daily_bdt, ca.active_days
FROM customer_avg ca
JOIN dim_customer c USING (customer_key)
ORDER BY ca.avg_daily_bdt DESC
LIMIT 100;
৬ · Subquery — কখন CTE-র চেয়ে ভালো
- Scalar subquery: WHERE-এ "average-এর চেয়ে বেশি"-র মতো single value compare।
- Correlated subquery: outer row-এর reference ব্যবহার করে — যেমন "এই customer-এর last order"।
- EXISTS / NOT EXISTS: existence check — JOIN-এর চেয়ে fast ও fan-out free।
-- যেসব customer-এর last 7 দিনে অন্তত একটি delivered order আছে
SELECT c.customer_id, c.name
FROM dim_customer c
WHERE EXISTS (
SELECT 1
FROM fact_orders o
WHERE o.customer_key = c.customer_key
AND o.order_ts >= CURRENT_DATE - INTERVAL '7 days'
AND o.status = 'delivered'
);
EXISTS match পেলেই থামে (semi-join) — তাই বড় টেবিলে IN/JOIN-এর চেয়ে অনেক fast। DE-এর দৈনন্দিন hammer।
৭ · Query plan ও EXPLAIN ANALYZE
একই query কখনো ১ সেকেন্ডে শেষ, কখনো ১০ মিনিট — পার্থক্য query planQuery Plandatabase optimizer যে ক্রমে JOIN ও filter execute করবে — সেই execution graph। EXPLAIN দিয়ে দেখা যায়, EXPLAIN ANALYZE বাস্তবে চালিয়ে timing দেখায়।-এ। DE-কে query plan পড়তে জানতেই হবে।
EXPLAIN ANALYZE
SELECT c.division, SUM(o.revenue_bdt)
FROM fact_orders o
JOIN dim_customer c USING (customer_key)
WHERE o.order_ts >= '2025-04-01'
GROUP BY c.division;
-- নমুনা output (Postgres):
-- HashAggregate (cost=12450..12455 rows=8 width=42) (actual time=320..320)
-- -> Hash Join (cost=850..11200 rows=250000) (actual time=18..280)
-- Hash Cond: (o.customer_key = c.customer_key)
-- -> Index Scan on fact_orders_order_ts_idx (cost=0..9800 rows=250000) (actual time=0.1..150)
-- Index Cond: (order_ts >= '2025-04-01')
-- -> Hash (cost=750..750 rows=8000) (actual time=15..15)
-- -> Seq Scan on dim_customer (cost=0..750 rows=8000)
-- Planning Time: 0.3 ms
-- Execution Time: 322 ms
৮ · Index — কখন কোন কোন column-এ
- Foreign keys: JOIN column-এ অবশ্যই index।
customer_key,product_key। - WHERE-এর filter column:
order_ts,status— যদি high cardinality থাকে। - Composite index:
(customer_key, order_ts)— leftmost prefix rule। - Partial index:
WHERE status = 'pending'— production-এ pending row কম, lookup fast। - না দিন: খুব low cardinality (boolean), constantly-updated column-এ overhead বেশি।
৯ · DE-specific SQL pattern
- Idempotent UPSERT: daily pipeline-এ rerun করলেও duplicate না হয় —
INSERT ... ON CONFLICT। - Date dimension JOIN: তারিখ-ভিত্তিক যেকোনো analytics-এ
dim_dateJOIN — calendar-aware reporting। - NULL safety:
COALESCE,NULLIF— pipeline-এ NULL leakage সবচেয়ে সাধারণ data quality bug। - CAST সাবধানে:
'২০২৫'::intBangla digit-এ fail করবে — input cleaning জরুরি।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ আপনার একটি Daraz query ৩০ মিনিট চলছে — production dashboard আটকে। EXPLAIN ANALYZE-এ কী দেখবেন, এবং কোন কোন স্পষ্ট red flag খুঁজবেন?
Production query timeout — DE-র সবচেয়ে stressful situation। সঠিক diagnosis order:
(১) প্রথমে EXPLAIN (without ANALYZE):
- ANALYZE আসলে query চালায় — যদি ৩০ মিনিট চলেই, আবার চালানো বিপজ্জনক। শুধু plan দেখুন।
- cost number (top node) huge হলে plan-ই খারাপ।
- estimated rows বাস্তবের সাথে wildly mismatch হলে statistics outdated।
(২) Red flag: Sequential Scan on big table:
Seq Scan on fact_orders— যদি filter আছে কিন্তু scan সব row পড়ে, index missing।- Solution:
order_tsবাcustomer_key-এ index। - Exception: টেবিল ছোট (~1000 rows) হলে seq scan ঠিক, optimizer বুদ্ধিমান।
(৩) Nested Loop Join on huge tables:
- Outer ৫ লাখ × inner ১০ লাখ-এ ৫ ট্রিলিয়ন lookup — অসম্ভব।
- Optimizer-এর Hash Join বা Merge Join বাছার কথা। যদি Nested Loop বাছছে, statistics ভুল হিসাব দিচ্ছে।
- Fix:
ANALYZE table_nameচালান, statistics refresh।
(৪) Estimated vs actual rows mismatch:
- Estimated 100 rows, actual 5,000,000 — এই ৫০,০০০x mismatch optimizer-কে completely ভুল pick করায়।
- Cause: stale stats, skewed data, complex filter expression।
- Fix: ANALYZE, increase
default_statistics_target, query rewrite।
(৫) JOIN order সমস্যা:
- বড় টেবিল × বড় টেবিল প্রথমে JOIN, তারপর small dim — explosion।
- Fix: filter আগে apply, ছোট টেবিল আগে।
- Sometimes
LEADINGhint ব্যবহার (Oracle) বা CTE দিয়ে force order।
(৬) Sort/Hash spill to disk:
- "external merge Disk: 500MB" — work_mem ছাড়িয়েছে, disk-এ spill।
- Fix:
SET work_mem = '256MB'session-এ; বা DISTINCT/ORDER BY-এর আগে rows কমান।
(৭) Cartesian product (CROSS JOIN accident):
- JOIN condition ভুলে গেলে — ১ লাখ × ১ লাখ = ১ ট্রিলিয়ন row।
- plan-এ "Nested Loop" without "Join Cond" big warning।
Production debugging triage:
- Kill the long-running query — production-এ priority।
- Lower environment-এ EXPLAIN দেখুন।
- Index, statistics, query rewrite — order-এ চেষ্টা।
- Persistent? — partition strategy বা materialized view consider করুন।
মূল উপলব্ধি: Performance debugging intuition আসে অভিজ্ঞতা থেকে। প্রতি new project-এ সবচেয়ে important ৫টা query-র plan নিজে পড়ুন — production-এ surprise কম।
প্র ০২ একই query — CTE বনাম subquery বনাম temp table দিয়ে লেখা যায়। কোন situation-এ কোনটি বাছবেন?
সঠিক উত্তর situation-dependent, এবং SQL engine-ভেদে আলাদা।
CTE (WITH) — default পছন্দ:
- Readability — অনেক step-এর pipeline-এ unmatched।
- Same logic একাধিকবার reference (modern engines auto-cache, পুরোনো-তে duplicate compute)।
- Recursive query (পরের পাঠ) শুধু CTE-তে possible।
- dbt model — পুরোটাই CTE chain।
- Limitation: Postgres ১১-এ optimization fence ছিল, ১২+-এ inlined।
Subquery — যখন কম্প্যাক্ট better:
- Single-use, simple —
SELECT *, (SELECT COUNT(*) FROM orders WHERE ...) AS cnt। - Correlated subquery — outer-এর row reference।
- EXISTS / NOT EXISTS-এ semi-join performance।
- সাবধান: nested ৩+ স্তর হলে CTE-তে refactor — পড়া অসম্ভব।
Temp table — heavy intermediate-এ:
- একই intermediate result অনেকবার লাগবে — CTE inline হলে recompute, temp table এক বার compute।
- Temp table-এ index দেওয়া যায় — CTE-তে সম্ভব না।
- Statistics generate হয় — optimizer better plan বাছে next step-এ।
- Cross-session sharing — session-scoped অথবা real table।
- Cost: disk write, transaction overhead, cleanup management।
Materialized CTE (Postgres 12+): WITH x AS MATERIALIZED (...) — CTE-কে temp-table-এর মতো force। যখন optimizer ভুল decision নেয়।
Decision tree:
- One-time query, ছোট — subquery/CTE indifferent।
- Multi-step, readable — CTE।
- Same intermediate ৩+ বার — temp table বা MATERIALIZED CTE।
- Recursive — must CTE।
- Production pipeline (Airflow, dbt) — CTE for code, table materialization for compute reuse।
BD context উদাহরণ:
- Daraz daily revenue dashboard — subquery enough।
- bKash fraud detection (multi-step suspicious pattern) — CTE chain।
- Pathao monthly settlement (intermediate ride totals reused for driver bonus, area metrics, fraud check) — temp table।
মূল কথা: "Best" বলে কিছু নেই। Readability + measured performance — দু'টোই বিবেচ্য। শুরুতে CTE; bottleneck হলে EXPLAIN দেখে উপযুক্ত alternative।
প্র ০৩ OLTP database (PostgreSQL) ও OLAP warehouse (BigQuery) — দু'টোতেই আমরা SQL লিখি। index strategy ও query optimization কিভাবে ভিন্ন?
এক ভাষা — দু'টা ভিন্ন জগৎ। DE-র জন্য এই পার্থক্য বোঝা critical।
OLTP (Postgres, MySQL) — row-store, transaction-optimized:
- প্রতিটি row একসাথে disk-এ — point lookup (single row read) cheap।
- B-tree index — primary tool, foreign key, status, timestamp-এ।
- Workload: ছোট transaction (Daraz checkout — ১০-২০ row read/write)।
- Optimization: ACID, lock contention, replication lag।
- Index rule: high cardinality, frequent filter → index।
OLAP (BigQuery, Snowflake, Redshift) — columnar, analytics-optimized:
- প্রতিটি column আলাদা compressed file। ১০০ column-এর টেবিলে শুধু ৩ column read করলে ৯৭% I/O save।
- Traditional B-tree index নেই — partition + cluster এর কাজ করে।
- Workload: full-table aggregation (১ বছরের ১০০ মিলিয়ন order summarize)।
- Optimization: partition pruning, column projection, predicate pushdown, broadcast vs shuffle JOIN।
- "Index" rule: partition by date (hot column), cluster by high-selectivity filter (customer_id, region)।
Concrete example — same query, different optimization:
Query: "গত ৭ দিনে চট্টগ্রামে fashion category-তে total revenue।"
- Postgres:
idx_orders_ts_status— order_ts range scan।idx_orders_customer_key— JOIN-এর জন্য।idx_customer_division— partial indexWHERE division = 'চট্টগ্রাম'।- ৭ দিনের data — ১-২ লাখ row, query ১-২ সেকেন্ডে।
- BigQuery:
PARTITION BY DATE(order_ts)— শুধু ৭টি partition scan।CLUSTER BY customer_id, product_category— block-level pruning।- কোনো traditional index নেই, কিন্তু ১ বিলিয়ন row-এ ৩-৫ সেকেন্ডে।
- Cost: bytes processed-এর ভিত্তিতে — partition pruning সরাসরি bill কমায়।
Other key differences:
- Update/Delete: OLTP cheap, OLAP costly (immutable storage, MERGE rewrites partition)।
- Concurrency: OLTP — হাজার concurrent connection। OLAP — ১০-১০০, কিন্তু প্রতিটি massive।
- JOIN: OLTP — Hash/Nested Loop। OLAP — distributed shuffle vs broadcast (small dim broadcast to all nodes)।
- Cost model: OLTP — row count। OLAP — bytes scanned (BigQuery), credit-seconds (Snowflake)।
- Latency: OLTP — ms। OLAP — sec to min, optimized for throughput not latency।
DE practical advice:
- OLTP read replica থেকে analytics — ছোট scale-এ কাজ চলে, বড় হলে OLAP লাগবেই।
- BigQuery-তে SELECT * করলে cost বিস্ফোরণ — শুধু দরকারি column।
- Snowflake warehouse size পরীক্ষা — auto-suspend ৫ মিনিট, না হলে credit বার্ন।
- Mixed workload — OLTP-এ analytic query চালালে production lock — read replica বা CDC দিয়ে warehouse-এ পাঠান।
মূল উপলব্ধি: SQL syntax একই, কিন্তু performance mental model সম্পূর্ণ আলাদা। DE-কে দু'টা সিস্টেমেই comfortable হতে হবে।
প্র ০৪ Grameenphone-এর CDR table-এ "৪G data session গত ১ ঘণ্টায় সবচেয়ে বেশি use করা ১০০ subscriber" query — billion-row scale-এ আপনি কীভাবে লিখবেন?
এই scale-এ "naive SQL" আর "production-grade SQL"-এর পার্থক্য বিরাট। বছরে ১৮০+ বিলিয়ন CDR row।
Naive (যা শুরুতে লেখা হয় — কাজ করে না):
SELECT msisdn, SUM(bytes_used) AS total_bytes
FROM fact_cdr
WHERE service_type = '4G_DATA'
AND session_ts >= NOW() - INTERVAL '1 hour'
GROUP BY msisdn
ORDER BY total_bytes DESC
LIMIT 100;
সমস্যা: partition pruning কাজ করছে কিনা নিশ্চিত না, full hour-এ ৪-৫ কোটি row scan, GROUP BY সব key-এ shuffle।
Production-grade approach:
(১) Partition pruning নিশ্চিত করুন:
- Table-এ
PARTITION BY DATE(session_ts)থাকা ধরা। - Filter explicit literal-এ —
NOW()dynamic, কিছু engine partition prune করতে পারে না। - Better:
session_ts BETWEEN '2025-05-09 14:00' AND '2025-05-09 15:00'।
(২) Pre-aggregated table ব্যবহার:
- Real-time pipeline-এ
agg_subscriber_hourly_data— প্রতি ঘণ্টা auto-refresh। - Naive query raw fact থেকে ৪ কোটি row → agg-এ ৫-৬ লাখ row।
- Latency drop: ৩০ সেকেন্ড → ১ সেকেন্ড।
(৩) Approximate top-K (cheap):
- BigQuery:
APPROX_TOP_COUNT(msisdn, 100)— sketches ব্যবহার, ১০x fast। - Top-100-এ ranking exactly accurate না হলেও বাস্তবে identical (একই গ্রাহক)।
- Alarming/billing-এ ব্যবহার করবেন না, dashboard-এ perfect।
(৪) Cluster key strategically:
CLUSTER BY service_type, msisdn— service_type filter-এ block prune।- Snowflake micro-partition / BigQuery clustering — billion row-এ ১০-৫০x speedup।
(৫) Streaming alternative:
- Real-time top-K-এর জন্য Kafka + Flink/ksqlDB — sliding window, sub-second update।
- Warehouse query — ৫-১৫ মিনিট latency acceptable হলে ঠিক, real-time alarming-এ না।
(৬) Materialized view:
CREATE MATERIALIZED VIEW agg_data_5min— incremental refresh।- Snowflake dynamic table, BigQuery materialized view — auto-maintenance।
- Storage cost বাড়ে, কিন্তু query cost ৯৫% নামে।
Final production query:
-- agg table দিয়ে: ১ ঘণ্টা scan কয়েক লাখ row
SELECT msisdn,
SUM(bytes_used) AS total_bytes,
SUM(session_cnt) AS sessions
FROM agg_subscriber_hourly_data
WHERE hour_bucket = DATE_TRUNC('hour', NOW() - INTERVAL '1 hour')
AND service_type = '4G_DATA'
GROUP BY msisdn
ORDER BY total_bytes DESC
LIMIT 100;
(৭) Cost ও SLA monitoring:
- BigQuery — bytes scanned alert (e.g., কেউ ১TB+ scan করলে notify)।
- Snowflake — credit consumption per query।
- Slow query log + dashboard — DE-র ongoing duty।
BD telco perspective: Grameenphone-এর peak hour-এ subscriber ranking — fraud detection, capacity planning, marketing — সবেতে লাগে। ভুল architecture মানে মাসে কোটি টাকা cloud bill। সঠিক pre-aggregation + clustering + materialized view stack — single most impactful skill telco DE-তে।
মূল উপলব্ধি: Big-data scale-এ "SQL জানা" যথেষ্ট না — SQL + storage layout + query plan + cost economics একসাথে — এটাই senior DE।
অনুশীলন
-
Anti-join লিখুন: Daraz-এ যেসব gold-tier customer-এর গত ৬ মাসে কোনো order নেই — তাদের তালিকা।
SELECT c.customer_id, c.name, c.signup_date FROM dim_customer c WHERE c.customer_tier = 'gold' AND NOT EXISTS ( SELECT 1 FROM fact_orders o WHERE o.customer_key = c.customer_key AND o.order_ts >= CURRENT_DATE - INTERVAL '6 months' );Alternative: LEFT JOIN ... IS NULL pattern-ও কাজ করে; EXISTS সাধারণত fast।
-
CTE chain লিখুন: Pathao-তে গত ৩০ দিনের প্রতি area-তে গড় ride duration বের করে — সেই গড় ৩০ মিনিটের বেশি যেসব area, তাদের লিস্ট দিন।
WITH ride_30d AS ( SELECT area_key, ride_duration_min FROM fact_ride WHERE ride_ts >= CURRENT_DATE - INTERVAL '30 days' AND status = 'completed' ), area_avg AS ( SELECT area_key, AVG(ride_duration_min) AS avg_min, COUNT(*) AS rides FROM ride_30d GROUP BY area_key HAVING AVG(ride_duration_min) > 30 ) SELECT a.area_name, a.district, ROUND(aa.avg_min, 1) AS avg_min, aa.rides FROM area_avg aa JOIN dim_area a USING (area_key) ORDER BY aa.avg_min DESC; -
Fan-out fix করুন:
fact_ordersওfact_returnsJOIN করে per-customer revenue ও return count চান। সঠিক query লিখুন।WITH return_per_order AS ( SELECT order_id, COUNT(*) AS return_cnt FROM fact_returns GROUP BY order_id ) SELECT o.customer_key, SUM(o.revenue_bdt) AS revenue, SUM(COALESCE(r.return_cnt,0)) AS returns FROM fact_orders o LEFT JOIN return_per_order r ON o.order_id = r.order_id GROUP BY o.customer_key;আগে return aggregate করে order-এ ১:১ JOIN — fan-out নেই, revenue accurate।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ০৭ · Window function ও advanced SQL পরবর্তী পাঠ ROW_NUMBER, LAG, MERGE, recursive CTE — DE-র "power tools"।
- পাঠ ০৫ · Schema design আগের পাঠ কোন schema-তে query করছেন তা বুঝলেই query সহজ।
- পাঠ ০৮ · PostgreSQL — open-source DB এই পাঠের সাথে সম্পর্কিত এই SQL সবচেয়ে জনপ্রিয় OLTP database — Postgres-এ practice।
- সব AI Courses দেখুন ABCL TECH Python, ML, DL, NLP, CV, GenAI, RL, MLOps — সব AI কোর্স একসাথে।