Skip to content

Added task 3497 #790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
3497\. Analyze Subscription Conversion

Medium

Table: `UserActivity`

+------------------+---------+
| Column Name | Type |
+------------------+---------+
| user_id | int |
| activity_date | date |
| activity_type | varchar |
| activity_duration| int |
+------------------+---------+
(user_id, activity_date, activity_type) is the unique key for this table. activity_type is one of ('free_trial', 'paid', 'cancelled').
activity_duration is the number of minutes the user spent on the platform that day.
Each row represents a user's activity on a specific date.

A subscription service wants to analyze user behavior patterns. The company offers a `7`\-day **free trial**, after which users can subscribe to a **paid plan** or **cancel**. Write a solution to:

1. Find users who converted from free trial to paid subscription
2. Calculate each user's **average daily activity duration** during their **free trial** period (rounded to `2` decimal places)
3. Calculate each user's **average daily activity duration** during their **paid** subscription period (rounded to `2` decimal places)

Return _the result table ordered by_ `user_id` _in **ascending** order_.

The result format is in the following example.

**Example:**

**Input:**

UserActivity table:

| user_id | activity_date | activity_type | activity_duration |
|---------|---------------|---------------|-------------------|
| 1 | 2023-01-01 | free_trial | 45 |
| 1 | 2023-01-02 | free_trial | 30 |
| 1 | 2023-01-05 | free_trial | 60 |
| 1 | 2023-01-10 | paid | 75 |
| 1 | 2023-01-12 | paid | 90 |
| 1 | 2023-01-15 | paid | 65 |
| 2 | 2023-02-01 | free_trial | 55 |
| 2 | 2023-02-03 | free_trial | 25 |
| 2 | 2023-02-07 | free_trial | 50 |
| 2 | 2023-02-10 | cancelled | 0 |
| 3 | 2023-03-05 | free_trial | 70 |
| 3 | 2023-03-06 | free_trial | 60 |
| 3 | 2023-03-08 | free_trial | 80 |
| 3 | 2023-03-12 | paid | 50 |
| 3 | 2023-03-15 | paid | 55 |
| 3 | 2023-03-20 | paid | 85 |
| 4 | 2023-04-01 | free_trial | 40 |
| 4 | 2023-04-03 | free_trial | 35 |
| 4 | 2023-04-05 | paid | 45 |
| 4 | 2023-04-07 | cancelled | 0 |

**Output:**

| user_id | trial_avg_duration | paid_avg_duration |
|---------|--------------------|-------------------|
| 1 | 45.00 | 76.67 |
| 3 | 70.00 | 63.33 |
| 4 | 37.50 | 45.00 |

**Explanation:**

* **User 1:**
* Had 3 days of free trial with durations of 45, 30, and 60 minutes.
* Average trial duration: (45 + 30 + 60) / 3 = 45.00 minutes.
* Had 3 days of paid subscription with durations of 75, 90, and 65 minutes.
* Average paid duration: (75 + 90 + 65) / 3 = 76.67 minutes.
* **User 2:**
* Had 3 days of free trial with durations of 55, 25, and 50 minutes.
* Average trial duration: (55 + 25 + 50) / 3 = 43.33 minutes.
* Did not convert to a paid subscription (only had free\_trial and cancelled activities).
* Not included in the output because they didn't convert to paid.
* **User 3:**
* Had 3 days of free trial with durations of 70, 60, and 80 minutes.
* Average trial duration: (70 + 60 + 80) / 3 = 70.00 minutes.
* Had 3 days of paid subscription with durations of 50, 55, and 85 minutes.
* Average paid duration: (50 + 55 + 85) / 3 = 63.33 minutes.
* **User 4:**
* Had 2 days of free trial with durations of 40 and 35 minutes.
* Average trial duration: (40 + 35) / 2 = 37.50 minutes.
* Had 1 day of paid subscription with duration of 45 minutes before cancelling.
* Average paid duration: 45.00 minutes.

The result table only includes users who converted from free trial to paid subscription (users 1, 3, and 4), and is ordered by user\_id in ascending order.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Write your MySQL query statement below
# #Medium #Database #2025_03_29_Time_347_ms_(100.00%)_Space_0.0_MB_(100.00%)
SELECT
ft.user_id,
ROUND(ft.avg_trial, 2) AS trial_avg_duration,
ROUND(pt.avg_paid, 2) AS paid_avg_duration
FROM
(SELECT user_id, AVG(activity_duration) AS avg_trial
FROM UserActivity
WHERE activity_type = 'free_trial'
GROUP BY user_id) ft
JOIN
(SELECT user_id, AVG(activity_duration) AS avg_paid
FROM UserActivity
WHERE activity_type = 'paid'
GROUP BY user_id) pt
ON ft.user_id = pt.user_id
ORDER BY ft.user_id ASC;
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package g3401_3500.s3497_analyze_subscription_conversion

import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert
import org.junit.jupiter.api.Test
import org.zapodot.junit.db.annotations.EmbeddedDatabase
import org.zapodot.junit.db.annotations.EmbeddedDatabaseTest
import org.zapodot.junit.db.common.CompatibilityMode
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.FileReader
import java.sql.ResultSet
import java.sql.SQLException
import java.util.stream.Collectors
import javax.sql.DataSource

@EmbeddedDatabaseTest(
compatibilityMode = CompatibilityMode.MySQL,
initialSqls = [
(
" CREATE TABLE UserActivity (" +
" user_id INT," +
" activity_date date," +
" activity_type VARCHAR(100)," +
" activity_duration INT" +
");" +
"INSERT INTO UserActivity (user_id, activity_date, activity_type, activity_duration)" +
"VALUES" +
" (1, '2023-01-01', 'free_trial', 45)," +
" (1, '2023-01-02', 'free_trial', 30)," +
" (1, '2023-01-05', 'free_trial', 60)," +
" (1, '2023-01-10', 'paid', 75)," +
" (1, '2023-01-12', 'paid', 90)," +
" (1, '2023-01-15', 'paid', 65)," +
" (2, '2023-02-01', 'free_trial', 55)," +
" (2, '2023-02-03', 'free_trial', 25)," +
" (2, '2023-02-07', 'free_trial', 50)," +
" (2, '2023-02-10', 'cancelled', 0)," +
" (3, '2023-03-05', 'free_trial', 70)," +
" (3, '2023-03-06', 'free_trial', 60)," +
" (3, '2023-03-08', 'free_trial', 80)," +
" (3, '2023-03-12', 'paid', 50)," +
" (3, '2023-03-15', 'paid', 55)," +
" (3, '2023-03-20', 'paid', 85)," +
" (4, '2023-04-01', 'free_trial', 40)," +
" (4, '2023-04-03', 'free_trial', 35)," +
" (4, '2023-04-05', 'paid', 45)," +
" (4, '2023-04-07', 'cancelled', 0);"
),
],
)
internal class MysqlTest {
@Test
@Throws(SQLException::class, FileNotFoundException::class)
fun testScript(@EmbeddedDatabase dataSource: DataSource) {
dataSource.connection.use { connection ->
connection.createStatement().use { statement ->
statement.executeQuery(
BufferedReader(
FileReader(
(
"src/main/kotlin/g3401_3500/" +
"s3497_analyze_subscription_conversion/" +
"script.sql"
),
),
)
.lines()
.collect(Collectors.joining("\n"))
.replace("#.*?\\r?\\n".toRegex(), ""),
).use { resultSet ->
checkRow(resultSet, arrayOf<String>("1", "45.0", "76.67"))
checkRow(resultSet, arrayOf<String>("3", "70.0", "63.33"))
checkRow(resultSet, arrayOf<String>("4", "37.5", "45.0"))
MatcherAssert.assertThat<Boolean>(resultSet.next(), CoreMatchers.equalTo<Boolean>(false))
}
}
}
}

@Throws(SQLException::class)
private fun checkRow(resultSet: ResultSet, values: Array<String>) {
MatcherAssert.assertThat<Boolean>(resultSet.next(), CoreMatchers.equalTo<Boolean>(true))
MatcherAssert.assertThat<String>(resultSet.getNString(1), CoreMatchers.equalTo<String>(values[0]))
MatcherAssert.assertThat<String>(resultSet.getNString(2), CoreMatchers.equalTo<String>(values[1]))
MatcherAssert.assertThat<String>(resultSet.getNString(3), CoreMatchers.equalTo<String>(values[2]))
}
}