Собесов

Домклик: SQL по городам, регионам и событиям

SQLJOIN и агрегацииСредняяJunior

Условие

Дана нормализованная схема:

  • countries(id, name)
  • regions(id, name, country_id)
  • cities(id, name, region_id, population)
  • events(id, city_id, type_id, date, costs)
  • types(id, name)

С помощью SQL получить:

  1. Страну, в которой находится город «Казань».
  2. Количество городов в Московской области.
  3. Количество уборок снега с декабря 2020 по февраль 2021.
  4. Городское население каждого региона.
  5. Количество уборок снега в Москве за последние 3 года.
  6. Средние траты на каждый тип события за 5 лет в Санкт-Петербурге.
  7. Среднее время между одинаковыми событиями для каждого города.
  8. Средняя стоимость трат по типу события на 1 человека в год для каждого региона.

Решение

-- 1) Страна, где Казань
SELECT co.name
FROM cities ci JOIN regions r ON r.id=ci.region_id
JOIN countries co ON co.id=r.country_id
WHERE ci.name = 'Казань';
 
-- 2) Города в Московской области
SELECT COUNT(*) FROM cities ci JOIN regions r ON r.id=ci.region_id
WHERE r.name = 'Московская область';
 
-- 3) Уборки снега 12.2020–02.2021
SELECT COUNT(*) FROM events e JOIN types t ON t.id=e.type_id
WHERE t.name = 'Уборка снега'
  AND e.date BETWEEN DATE '2020-12-01' AND DATE '2021-02-28';
 
-- 4) Городское население региона
SELECT r.name, SUM(ci.population) AS urban_pop
FROM cities ci JOIN regions r ON r.id=ci.region_id
GROUP BY r.name;
 
-- 5) Уборки снега в Москве за 3 года
SELECT COUNT(*) FROM events e
JOIN cities ci ON ci.id = e.city_id
JOIN types t ON t.id = e.type_id
WHERE ci.name='Москва' AND t.name='Уборка снега'
  AND e.date >= CURRENT_DATE - INTERVAL '3 years';
 
-- 6) Средние траты по типу события в СПб за 5 лет
SELECT t.name AS type, AVG(e.costs) AS avg_cost
FROM events e
JOIN cities ci ON ci.id=e.city_id
JOIN types t   ON t.id=e.type_id
WHERE ci.name='Санкт-Петербург'
  AND e.date >= CURRENT_DATE - INTERVAL '5 years'
GROUP BY t.name;
 
-- 7) Среднее время между одинаковыми событиями по городам
WITH ord AS (
  SELECT
    e.city_id, e.type_id, e.date,
    LAG(e.date) OVER (PARTITION BY e.city_id, e.type_id ORDER BY e.date) AS prev_date
  FROM events e
)
SELECT ci.name, t.name,
       AVG(EXTRACT(EPOCH FROM (date - prev_date))/86400) AS avg_days_between
FROM ord
JOIN cities ci ON ci.id=ord.city_id
JOIN types t   ON t.id=ord.type_id
WHERE prev_date IS NOT NULL
GROUP BY ci.name, t.name;
 
-- 8) Стоимость трат на 1 человека в год по региону × тип события
WITH yearly AS (
  SELECT r.id AS region_id, t.name AS type, SUM(e.costs) AS total,
         COUNT(DISTINCT EXTRACT(YEAR FROM e.date)) AS years
  FROM events e
  JOIN cities ci ON ci.id=e.city_id
  JOIN regions r ON r.id=ci.region_id
  JOIN types t   ON t.id=e.type_id
  GROUP BY r.id, t.name
),
pop AS (
  SELECT region_id, SUM(population) AS pop FROM cities GROUP BY region_id
)
SELECT r.name AS region, y.type,
       y.total / y.years / NULLIF(p.pop, 0) AS cost_per_person_per_year
FROM yearly y
JOIN pop p     ON p.region_id = y.region_id
JOIN regions r ON r.id = y.region_id;

Подводные камни

  1. Период «последние 3 года» можно понимать как «3 календарных» или «текущий + 2 предыдущих» — уточнять.
  2. В пункте 7 для пар событий внутри одного дня возможен 0; уточнить, считаем ли в часах.
  3. Население — на дату записи; на длинной истории нужен срез на конкретную дату.
  4. NULL costs или population искажает средние — COALESCE или WHERE NOT NULL.

Эталонный ответ

Серия JOIN-ов и агрегаций; среднее время между событиями — через LAG() в окне (city_id, type_id) ORDER BY date.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти