दिन 33: SQL Joins, GROUP BY और HAVING
✅ आज आप सीखेंगे
- Aggregate functions और GROUP BY
- HAVING vs WHERE
- INNER, LEFT, RIGHT/FULL joins
- Self-join, UNION और sub-queries
- SQL और pandas के बीच अनुवाद
🏢 असली दुनिया में
HR head पूछते हैं: “हर department और office location का head-count और औसत salary दिखाओ – और जो किसी department से नहीं जुड़े, उनकी list।” Finance पूछता है: “किन शहरों ने prepaid revenue में ₹6 lakh पार किया?” दोनों GROUP BY + JOIN सवाल हैं – हर SQL interview का रोज़ का खाना।
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
pd.read_csv("sales_data.csv").to_sql("sales", con, if_exists="replace", index=False)
pd.read_csv("employees.csv").to_sql("employees", con, if_exists="replace", index=False)
pd.read_csv("departments.csv").to_sql("departments", con, if_exists="replace", index=False)
print([r[0] for r in con.execute("SELECT name FROM sqlite_master WHERE type = 'table'")])Aggregates और GROUP BY
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT COUNT(*) AS orders,
COUNT(rating) AS rated_orders,
COUNT(DISTINCT city) AS cities,
ROUND(SUM(quantity * unit_price * (1 - discount))) AS revenue,
ROUND(AVG(rating), 2) AS avg_rating,
MIN(order_date) AS first_day, MAX(order_date) AS last_day
FROM sales
"""))import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT region,
COUNT(*) AS orders,
ROUND(SUM(quantity * unit_price * (1 - discount)) / 100000.0, 2) AS revenue_lakh,
ROUND(AVG(quantity * unit_price * (1 - discount))) AS aov
FROM sales
GROUP BY region
ORDER BY revenue_lakh DESC
"""))SELECT का हर column या तो GROUP BY में हो या aggregate (SUM, AVG, COUNT…) के अंदर। SELECT city, region, SUM(...) ... GROUP BY city ज़्यादातर databases में error है।
HAVING – groups filter करना
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT city,
ROUND(SUM(quantity * unit_price * (1 - discount)) / 100000.0, 2) AS revenue_lakh
FROM sales
WHERE payment_mode <> 'Cash on Delivery' -- row filter (before grouping)
GROUP BY city
HAVING SUM(quantity * unit_price * (1 - discount)) > 600000 -- group filter
ORDER BY revenue_lakh DESC
"""))JOINs
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT e.name, e.salary, d.dept_name, d.location
FROM employees AS e
INNER JOIN departments AS d ON e.dept_id = d.dept_id
ORDER BY e.salary DESC
LIMIT 5
"""))
print(q("SELECT COUNT(*) AS inner_rows FROM employees e JOIN departments d ON e.dept_id = d.dept_id"))import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT e.emp_id, e.name, e.dept_id, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
WHERE d.dept_id IS NULL -- employees with no matching department
"""))import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT COALESCE(d.dept_name, 'Unassigned') AS department,
COALESCE(d.location, '-') AS location,
COUNT(e.emp_id) AS headcount,
ROUND(AVG(e.salary)) AS avg_salary
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
GROUP BY department, location
ORDER BY headcount DESC
"""))import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT d.dept_name, COUNT(e.emp_id) AS headcount
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.dept_id
GROUP BY d.dept_name
UNION ALL
SELECT 'No department', COUNT(*) FROM employees
WHERE dept_id NOT IN (SELECT dept_id FROM departments)
"""))PostgreSQL और SQLite 3.39+ सीधे FULL OUTER JOIN support करते हैं। MySQL नहीं – ऊपर जैसा LEFT JOIN और RIGHT JOIN को UNION से जोड़ें।
Self-join और sub-queries
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT a.name AS employee, b.name AS colleague, a.city
FROM employees a
JOIN employees b ON a.city = b.city AND a.emp_id < b.emp_id AND a.dept_id = b.dept_id
WHERE a.city = 'Pune'
"""))import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""
SELECT name, salary, dept_id
FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees x WHERE x.dept_id = e.dept_id) -- correlated sub-query
ORDER BY dept_id, salary DESC
LIMIT 8
"""))SQL ↔ pandas cheat sheet
| SQL | pandas |
|---|---|
| SELECT a, b FROM t | df[["a", "b"]] |
| WHERE city = 'Pune' | df[df.city == "Pune"] |
| GROUP BY city … SUM(x) | df.groupby("city")["x"].sum() |
| HAVING SUM(x) > 10 | .loc[lambda s: s > 10] after groupby |
| ORDER BY x DESC LIMIT 5 | df.nlargest(5, "x") |
| LEFT JOIN … ON k | df.merge(other, on="k", how="left") |
| UNION ALL | pd.concat([a, b]) |
| COALESCE(x, 0) | df.x.fillna(0) |
🏅 आज के Job Skills
Practice tasks
🏋️ Practice – ख़ुद करें
Solution देखें
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""SELECT category, payment_mode, COUNT(*) AS orders, ROUND(SUM(quantity*unit_price*(1-discount))) AS revenue
FROM sales GROUP BY category, payment_mode HAVING COUNT(*) >= 100 ORDER BY revenue DESC"""))Solution देखें
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""SELECT d.location, SUM(e.salary) AS cost, COUNT(*) AS people FROM employees e
JOIN departments d ON e.dept_id = d.dept_id GROUP BY d.location ORDER BY cost DESC"""))Solution देखें
import sqlite3
import pandas as pd
con = sqlite3.connect("bazaar.db")
q = lambda sql: pd.read_sql(sql, con)
print(q("""SELECT d.dept_name, e.name, e.salary FROM employees e JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary = (SELECT MAX(salary) FROM employees x WHERE x.dept_id = e.dept_id) ORDER BY e.salary DESC"""))Quick quiz
🧠 Quick Quiz – ख़ुद को जाँचें
Interview corner
💼 Interview Corner
SQL joins के प्रकार समझाइए।
INNER दोनों tables की matching rows देता है; LEFT left table की सभी rows और matches (वरना NULL); RIGHT उसका उल्टा; FULL OUTER दोनों की सभी rows; CROSS हर combination; self-join table को ख़ुद से जोड़ता है।
5 से ज़्यादा employees वाले departments ढूँढें।
SELECT dept_id, COUNT(*) FROM employees GROUP BY dept_id HAVING COUNT(*) > 5; – नाम के लिए departments join करें।
सारांश
- GROUP BY + aggregates summary देते हैं; HAVING groups filter करता है।
- INNER = matches, LEFT = left की सभी rows, FULL = सब कुछ।
- COALESCE NULLs भरता है; UNION ALL results stack करता है।
- Sub-queries row की उसके group से तुलना करती हैं।
Day 34: window functions और CTEs – SQL में rankings, running totals और month-over-month growth।
