Skip to content
178 changes: 178 additions & 0 deletions LeetCode SQL 50 Solution/1280. Students and Examinations/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# 🎓 Find the Number of Times Each Student Attended Each Exam - LeetCode 1204

## 📌 Problem Statement
You are given three tables: **Students**, **Subjects**, and **Examinations**, which contain information about students, subjects, and exam attendance.

### 📊 Students Table
| Column Name | Type |
| ------------ | ------- |
| student_id | int |
| student_name | varchar |
- `student_id` is the **primary key**.
- Each row represents a **unique student**.

### 📊 Subjects Table
| Column Name | Type |
| ------------ | ------- |
| subject_name | varchar |
- `subject_name` is the **primary key**.
- Each row represents a **unique subject**.

### 📊 Examinations Table
| Column Name | Type |
| ------------ | ------- |
| student_id | int |
| subject_name | varchar |
- **No primary key** (may contain duplicates).
- Each row indicates that a student attended an exam for a specific subject.

### 🔢 Goal:
Find the **number of times each student attended each exam**.
If a student did **not attend an exam**, return `0`.
Return the results **ordered by** `student_id` and `subject_name`.

---

## 📊 Example 1:
### Input:

### **Students Table**
| student_id | student_name |
| ---------- | ------------ |
| 1 | Alice |
| 2 | Bob |
| 13 | John |
| 6 | Alex |

### **Subjects Table**
| subject_name |
| ------------ |
| Math |
| Physics |
| Programming |

### **Examinations Table**
| student_id | subject_name |
| ---------- | ------------ |
| 1 | Math |
| 1 | Physics |
| 1 | Programming |
| 2 | Programming |
| 1 | Physics |
| 1 | Math |
| 13 | Math |
| 13 | Programming |
| 13 | Physics |
| 2 | Math |
| 1 | Math |

### Output:
| student_id | student_name | subject_name | attended_exams |
| ---------- | ------------ | ------------ | -------------- |
| 1 | Alice | Math | 3 |
| 1 | Alice | Physics | 2 |
| 1 | Alice | Programming | 1 |
| 2 | Bob | Math | 1 |
| 2 | Bob | Physics | 0 |
| 2 | Bob | Programming | 1 |
| 6 | Alex | Math | 0 |
| 6 | Alex | Physics | 0 |
| 6 | Alex | Programming | 0 |
| 13 | John | Math | 1 |
| 13 | John | Physics | 1 |
| 13 | John | Programming | 1 |

---

## 🖥 SQL Solution

### 1️⃣ Standard MySQL Query
#### **Explanation:**
- **Use `CROSS JOIN`** to generate **all possible student-subject combinations**.
- **Use `LEFT JOIN`** to attach attendance records from `Examinations`.
- **Use `COUNT(e.subject_name)`** to count how many times a student attended an exam.
- **Sort results by** `student_id` and `subject_name`.

```sql
SELECT s.student_id, s.student_name, sb.subject_name,
COUNT(e.subject_name) AS attended_exams
FROM Students s
CROSS JOIN Subjects sb
LEFT JOIN Examinations e
ON s.student_id = e.student_id AND sb.subject_name = e.subject_name
GROUP BY s.student_id, sb.subject_name
ORDER BY s.student_id, sb.subject_name;
```

---

### 2️⃣ Alternative SQL Query (Using `COALESCE`)
```sql
SELECT s.student_id, s.student_name, sb.subject_name,
COALESCE(COUNT(e.subject_name), 0) AS attended_exams
FROM Students s
CROSS JOIN Subjects sb
LEFT JOIN Examinations e
ON s.student_id = e.student_id AND sb.subject_name = e.subject_name
GROUP BY s.student_id, sb.subject_name
ORDER BY s.student_id, sb.subject_name;
```
- **Uses `COALESCE(COUNT(...), 0)`** to explicitly handle `NULL` values.

---

### 3️⃣ Alternative SQL Query (Using `WITH ROLLUP`)
```sql
SELECT s.student_id, s.student_name, sb.subject_name,
COUNT(e.subject_name) AS attended_exams
FROM Students s
CROSS JOIN Subjects sb
LEFT JOIN Examinations e
ON s.student_id = e.student_id AND sb.subject_name = e.subject_name
GROUP BY s.student_id, sb.subject_name WITH ROLLUP
HAVING GROUPING(subject_name) = 0
ORDER BY s.student_id, sb.subject_name;
```
- **Uses `WITH ROLLUP`** to generate **aggregated results**.

---

## 🐍 Pandas Solution (Python)
#### **Explanation:**
- **Generate all possible (student, subject) pairs** using `pd.merge()`.
- **Group by student_id and subject_name**, then count occurrences.
- **Fill missing values with `0`**.

```python
import pandas as pd

def student_exam_attendance(students: pd.DataFrame, subjects: pd.DataFrame, exams: pd.DataFrame) -> pd.DataFrame:
# Create all possible student-subject combinations
all_combinations = students.merge(subjects, how="cross")

# Merge with exam data
merged = all_combinations.merge(exams, on=["student_id", "subject_name"], how="left")

# Count the number of times each student attended each exam
result = merged.groupby(["student_id", "student_name", "subject_name"]).size().reset_index(name="attended_exams")

return result.sort_values(["student_id", "subject_name"])
```

---

## 📁 File Structure
```
📂 Student-Exam-Attendance
│── 📜 README.md
│── 📜 solution.sql
│── 📜 solution_pandas.py
│── 📜 test_cases.sql
```

---

## 🔗 Useful Links
- 📖 [LeetCode Problem](https://leetcode.com/problems/find-the-number-of-times-each-student-attended-each-exam/)
- 📚 [SQL `CROSS JOIN` Documentation](https://www.w3schools.com/sql/sql_join_cross.asp)
- 🐍 [Pandas Merge Documentation](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.merge.html)
171 changes: 171 additions & 0 deletions LeetCode SQL 50 Solution/1321. Restaurant Growth/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# 🍽️ Restaurant Growth - LeetCode 321

## 📌 Problem Statement
You are given a table **Customer**, which records daily customer transactions in a restaurant.
The restaurant owner wants to analyze a **7-day moving average** of customer spending.

### 📊 Customer Table
| Column Name | Type |
| ----------- | ------- |
| customer_id | int |
| name | varchar |
| visited_on | date |
| amount | int |
- **(customer_id, visited_on) is the primary key**.
- `visited_on` represents the date a customer visited the restaurant.
- `amount` represents the total amount paid by a customer on that day.

---

## 🔢 Goal:
Compute the **7-day moving average** of customer spending.
- The window consists of **current day + 6 days before**.
- `average_amount` should be **rounded to 2 decimal places**.
- The result should be **ordered by `visited_on` in ascending order**.

---

## 📊 Example 1:
### **Input:**
#### **Customer Table**
| customer_id | name | visited_on | amount |
| ----------- | ------- | ---------- | ------ |
| 1 | Jhon | 2019-01-01 | 100 |
| 2 | Daniel | 2019-01-02 | 110 |
| 3 | Jade | 2019-01-03 | 120 |
| 4 | Khaled | 2019-01-04 | 130 |
| 5 | Winston | 2019-01-05 | 110 |
| 6 | Elvis | 2019-01-06 | 140 |
| 7 | Anna | 2019-01-07 | 150 |
| 8 | Maria | 2019-01-08 | 80 |
| 9 | Jaze | 2019-01-09 | 110 |
| 1 | Jhon | 2019-01-10 | 130 |
| 3 | Jade | 2019-01-10 | 150 |

### **Output:**
| visited_on | amount | average_amount |
| ---------- | ------ | -------------- |
| 2019-01-07 | 860 | 122.86 |
| 2019-01-08 | 840 | 120 |
| 2019-01-09 | 840 | 120 |
| 2019-01-10 | 1000 | 142.86 |

### **Explanation:**
1. **First moving average (2019-01-01 to 2019-01-07)**
\[
(100 + 110 + 120 + 130 + 110 + 140 + 150) / 7 = 122.86
\]
2. **Second moving average (2019-01-02 to 2019-01-08)**
\[
(110 + 120 + 130 + 110 + 140 + 150 + 80) / 7 = 120
\]
3. **Third moving average (2019-01-03 to 2019-01-09)**
\[
(120 + 130 + 110 + 140 + 150 + 80 + 110) / 7 = 120
\]
4. **Fourth moving average (2019-01-04 to 2019-01-10)**
\[
(130 + 110 + 140 + 150 + 80 + 110 + 130 + 150) / 7 = 142.86
\]

---

## 🖥 SQL Solutions

### 1️⃣ **Using `WINDOW FUNCTION` (`SUM() OVER` + `RANK() OVER`)**
#### **Explanation:**
- First, **group transactions per day** using `SUM(amount)`.
- Then, use `SUM() OVER (ROWS 6 PRECEDING)` to calculate **moving sum** over 7 days.
- Use `RANK()` to track row numbers and filter rows with `rk > 6`.
- Finally, compute `ROUND(amount / 7, 2)`.

```sql
WITH t AS (
SELECT
visited_on,
SUM(amount) OVER (
ORDER BY visited_on
ROWS 6 PRECEDING
) AS amount,
RANK() OVER (
ORDER BY visited_on
ROWS 6 PRECEDING
) AS rk
FROM (
SELECT visited_on, SUM(amount) AS amount
FROM Customer
GROUP BY visited_on
) AS tt
)
SELECT visited_on, amount, ROUND(amount / 7, 2) AS average_amount
FROM t
WHERE rk > 6;
```

---

### 2️⃣ **Using `JOIN` + `DATEDIFF()`**
#### **Explanation:**
- Use a **self-join** to find transactions **within a 7-day range**.
- Sum the `amount` for each window and calculate the moving average.
- Use `DATEDIFF(a.visited_on, b.visited_on) BETWEEN 0 AND 6` to filter records.
- Ensure only complete 7-day windows are included.

```sql
SELECT
a.visited_on,
SUM(b.amount) AS amount,
ROUND(SUM(b.amount) / 7, 2) AS average_amount
FROM
(SELECT DISTINCT visited_on FROM customer) AS a
JOIN customer AS b
ON DATEDIFF(a.visited_on, b.visited_on) BETWEEN 0 AND 6
WHERE
a.visited_on >= (SELECT MIN(visited_on) FROM customer) + 6
GROUP BY a.visited_on
ORDER BY a.visited_on;
```

---

## 🐍 Pandas Solution (Python)
#### **Explanation:**
- **Group by `visited_on`** and sum `amount` per day.
- **Use `.rolling(7).sum()`** to compute the moving sum over 7 days.
- **Drop NaN values** to exclude incomplete windows.
- **Round the average to 2 decimal places**.

```python
import pandas as pd

def restaurant_growth(customers: pd.DataFrame) -> pd.DataFrame:
# Aggregate daily amounts
daily_amount = customers.groupby("visited_on")["amount"].sum().reset_index()

# Compute rolling 7-day sum and moving average
daily_amount["amount"] = daily_amount["amount"].rolling(7).sum()
daily_amount["average_amount"] = (daily_amount["amount"] / 7).round(2)

# Drop incomplete windows
daily_amount = daily_amount.dropna().reset_index(drop=True)

return daily_amount
```

---

## 📁 File Structure
```
📂 Restaurant-Growth
│── 📜 README.md
│── 📜 solution.sql
│── 📜 solution_pandas.py
│── 📜 test_cases.sql
```

---

## 🔗 Useful Links
- 📖 [LeetCode Problem](https://leetcode.com/problems/restaurant-growth/)
- 📚 [SQL `WINDOW FUNCTIONS` Documentation](https://www.w3schools.com/sql/sql_window.asp)
- 🐍 [Pandas Rolling Window](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html)
Loading