7 SQL задач, которые задают на собеседовании в телеком-компании – Часть 2
Продолжаем разминать мозги и дорешиваем оставшиеся 3 из 7 задач на SQL. Первую часть разбора вы можете найти по ссылке.
1. Существует таблица tbl_terminal_device связи мобильного номера и устройства imei.
select * from tbl_terminal_device| mob_num | imei | created_at |
|---|---|---|
| 7900000000 | 868359022680 | 2021-04-13 |
| 7910000000 | 355428071779 | 2021-04-14 |
| 7920000000 | 353582037493 | 2021-04-15 |
| 7990000000 | 355428071779 | 2021-04-13 |
| 7910000000 | 353582037493 | 2021-04-22 |
При каждой замене устройства абонентом фиксируется новая дата создания связки номера с этим устройством. На текущий момент актуальна одна последняя связка с последним устройством, т.е. последняя по дате создания created_dt.
По первым 8 символам в идентификаторе устройства imei можно определить модель устройства tac.
Необходимо отобрать всех абонентов, у которых последнее устройство Samsung, т.е. tac = 12345678.
Воспользуемся CTE с оконной функцией ROW_NUMBER() с партицированием по mob_num абонента и сортировкой по убыванию даты:
WITH devices AS ( SELECT mob_num, imei, created_dt, ROW_NUMBER() OVER ( PARTITION BY mob_num ORDER BY created_dt DESC ) AS rn FROM tbl_terminal_device)Таким образом мы сможем впоследствии без труда извлекать последнюю дату и устройство для каждого абонента.
После, в основном запросе оставим только последнюю актуальную запись для каждого абонента (rn = 1) и воспользуемся функцией SUBSTRING, чтобы извлечь первые 8 символов IMEI и отфильтровать по ним:
WITH devices AS ( ...)SELECT mob_num, imei, created_dtFROM devicesWHERE rn = 1 AND SUBSTRING(imei, 1, 8) = '12345678'Готово: отобрали всех абонентов, у которых последнее устройство — Samsung.
2. Существует таблица EXAM_RESULTS с результатами последней завершенной сессии.
select * from EXAM_RESULTS| student | subject | test_dt | score | done |
|---|---|---|---|---|
| 77529 | матан | 18.06.2022 | 5 | [NULL] |
| 77529 | черчение | 11.06.2022 | 2 | [NULL] |
| 77529 | черчение | 17.06.2022 | 5 | [NULL] |
| 77529 | англ.яз | 16.06.2022 | 5 | [NULL] |
| 77529 | культуролог | 09.06.2022 | [NULL] | "+" |
| 77529 | ОБЖ | 19.06.2022 | [NULL] | "-" |
Необходимо найти студентов, которые получили на всех зачетах + и 5 на всех экзаменах.
Для этой задачи явно требуется задать уточняющие вопросы интервьюеру. Мы же будем исходить из следующих условий:
- Актуальной оценкой по предмету считается наивысшая.
- У каждого студента может быть свое количество дисциплин.
Через CTE для каждого студента найдем общее количество дисциплин и сколько дисциплин было сдано на 5 или с зачетом:
WITH students AS ( SELECT student, count(DISTINCT subject) as subjects, count(CASE WHEN score = 5 or done = '+' THEN subject ELSE NULL END) AS subjects_passed GROUP BY student)Далее выберем тех студентов, у которых количество сданных на 5 и зачтенных предметов равно количеству доступных для студента дисциплин.
Итоговое решение выглядит так:
WITH students AS ( SELECT student, count(DISTINCT subject) as subjects, count(CASE WHEN score = 5 or done = '+' THEN subject ELSE NULL END) AS subjects_passed GROUP BY student)SELECT studentFROM studentsWHERE subjects = subjects_passed3. Существует таблица абонентов appn_history с историей изменений. Каждая запись по абоненту действует в пределах периода с valid_from_dt по valid_to_dt. По каждому абоненту, в момент времени, должна быть только одна действующая запись.
select * from appn_history| appn | valid_from_dt | valid_to_dt | |
|---|---|---|---|
| 0 | abnt 1 | 2020-01-03 | 2020-02-20 |
| 1 | abnt 1 | 2020-02-21 | 2020-03-10 |
| 2 | abnt 1 | 2020-03-08 | 2020-05-15 |
| 3 | abnt 1 | 2020-05-16 | 2020-07-26 |
| 4 | abnt 2 | 2019-02-10 | 2020-04-12 |
| 5 | abnt 2 | 2019-02-10 | 2020-04-12 |
Необходимо написать запрос, отбирающий записи, которые нарушают условие хранения данных в таблице.
| appn | valid_from_dt | valid_to_dt | |
|---|---|---|---|
| 0 | abnt 1 | 2020-02-21 | 2020-03-10 |
| 1 | abnt 2 | 2019-02-10 | 2020-04-12 |
Эту задачу можно решить огромным количеством способов с разной степенью оптимальности.
Предлагаю следующее решение:
Для каждого пользователя пронумеруем каждый период при помощи функции ROW_NUMBER():
WITH sorted_history AS ( SELECT appn, valid_from_dt, valid_to_dt, ROW_NUMBER() OVER ( PARTITION BY appn ORDER BY valid_from_dt ) AS rn FROM appn_history)Для каждого абонента сравним каждую запись с предыдущей при помощи SELF JOIN и отфильтруем пересечения:
WITH sorted_history AS ( ...)SELECT t1.appn, t1.valid_from_dt, t1.valid_to_dtFROM sorted_history t1INNER JOIN sorted_history t2ON t1.appn = t2.appnAND t1.rn = t2.rn - 1AND t1.valid_to_dt >= t2.valid_from_dtAND t1.valid_from_dt <= t2.valid_to_dtГотовишься к собеседованию на аналитика?
Посмотри базу реальных тестовых заданий и разборы кейсов.