Это вторая неделя моего 8-недельного SQL-приключения от 8weeksqlchallenge.com, где я перейду от нуля к герою в SQL.
Прежде чем углубиться в эту статью, если вы пропустили решения задач первой недели, нажмите здесь.
Ставьте Нравится, делитесь и подписывайтесь на меня на Medium, чтобы получать больше интересных статей на темы Data Analytics, Data Viz, AI & ML и т. д.
В этой статье я поделюсь с вами своим подходом ко второй задаче: решению примера Pizza Runner, вымышленного ресторана, который предлагает и доставляет пиццу и хочет уберизировать ее. Если вы заинтересованы присоединиться ко мне в этом путешествии или хотите проверить свои навыки SQL, вы можете прочитать оригинальный пример из практики здесь.
Введение:
Знаете ли вы, что ежедневно во всем мире потребляется более 115 миллионов килограммов пиццы??? (Ну, согласно Википедии, во всяком случае…)
Дэнни просматривал свою ленту в Instagram, когда что-то действительно привлекло его внимание — «Ретро-стиль 80-х и пицца — это будущее!»
Дэнни увлекся этой идеей, но он знал, что пицца сама по себе не поможет ему получить начальное финансирование для расширения своей новой Империи пиццы, поэтому у него была еще одна гениальная идея, которую он мог объединить с ней — он собирался уберизовать это — и вот Pizza Runner был запущен!
Дэнни начал с найма «бегунов» для доставки свежей пиццы из штаб-квартиры Pizza Runner (также известной как дом Дэнни), а также полностью исчерпал свою кредитную карту, чтобы заплатить внештатным разработчикам за создание мобильного приложения для приема заказов от клиентов.
Доступные данные:
В этой задаче 6 таблиц. Таблицы следующие:
runners
customer_orders
runner_orders
pizza_names
pizza_recipes
pizza_toppings
База данных и таблицы:
Код для создания необходимой базы данных и таблиц приведен здесь.
CREATE SCHEMA pizza_runner; SET search_path = pizza_runner; DROP TABLE IF EXISTS runners; CREATE TABLE runners ( "runner_id" INTEGER, "registration_date" DATE ); INSERT INTO runners ("runner_id", "registration_date") VALUES (1, '2021-01-01'), (2, '2021-01-03'), (3, '2021-01-08'), (4, '2021-01-15'); DROP TABLE IF EXISTS customer_orders; CREATE TABLE customer_orders ( "order_id" INTEGER, "customer_id" INTEGER, "pizza_id" INTEGER, "exclusions" VARCHAR(4), "extras" VARCHAR(4), "order_time" TIMESTAMP ); INSERT INTO customer_orders ("order_id", "customer_id", "pizza_id", "exclusions", "extras", "order_time") VALUES ('1', '101', '1', '', '', '2020-01-01 18:05:02'), ('2', '101', '1', '', '', '2020-01-01 19:00:52'), ('3', '102', '1', '', '', '2020-01-02 23:51:23'), ('3', '102', '2', '', NULL, '2020-01-02 23:51:23'), ('4', '103', '1', '4', '', '2020-01-04 13:23:46'), ('4', '103', '1', '4', '', '2020-01-04 13:23:46'), ('4', '103', '2', '4', '', '2020-01-04 13:23:46'), ('5', '104', '1', 'null', '1', '2020-01-08 21:00:29'), ('6', '101', '2', 'null', 'null', '2020-01-08 21:03:13'), ('7', '105', '2', 'null', '1', '2020-01-08 21:20:29'), ('8', '102', '1', 'null', 'null', '2020-01-09 23:54:33'), ('9', '103', '1', '4', '1, 5', '2020-01-10 11:22:59'), ('10', '104', '1', 'null', 'null', '2020-01-11 18:34:49'), ('10', '104', '1', '2, 6', '1, 4', '2020-01-11 18:34:49'); DROP TABLE IF EXISTS runner_orders; CREATE TABLE runner_orders ( "order_id" INTEGER, "runner_id" INTEGER, "pickup_time" VARCHAR(19), "distance" VARCHAR(7), "duration" VARCHAR(10), "cancellation" VARCHAR(23) ); INSERT INTO runner_orders ("order_id", "runner_id", "pickup_time", "distance", "duration", "cancellation") VALUES ('1', '1', '2020-01-01 18:15:34', '20km', '32 minutes', ''), ('2', '1', '2020-01-01 19:10:54', '20km', '27 minutes', ''), ('3', '1', '2020-01-03 00:12:37', '13.4km', '20 mins', NULL), ('4', '2', '2020-01-04 13:53:03', '23.4', '40', NULL), ('5', '3', '2020-01-08 21:10:57', '10', '15', NULL), ('6', '3', 'null', 'null', 'null', 'Restaurant Cancellation'), ('7', '2', '2020-01-08 21:30:45', '25km', '25mins', 'null'), ('8', '2', '2020-01-10 00:15:02', '23.4 km', '15 minute', 'null'), ('9', '2', 'null', 'null', 'null', 'Customer Cancellation'), ('10', '1', '2020-01-11 18:50:20', '10km', '10minutes', 'null'); DROP TABLE IF EXISTS pizza_names; CREATE TABLE pizza_names ( "pizza_id" INTEGER, "pizza_name" TEXT ); INSERT INTO pizza_names ("pizza_id", "pizza_name") VALUES (1, 'Meatlovers'), (2, 'Vegetarian'); DROP TABLE IF EXISTS pizza_recipes; CREATE TABLE pizza_recipes ( "pizza_id" INTEGER, "toppings" TEXT ); INSERT INTO pizza_recipes ("pizza_id", "toppings") VALUES (1, '1, 2, 3, 4, 5, 6, 8, 10'), (2, '4, 6, 7, 9, 11, 12'); DROP TABLE IF EXISTS pizza_toppings; CREATE TABLE pizza_toppings ( "topping_id" INTEGER, "topping_name" TEXT ); INSERT INTO pizza_toppings ("topping_id", "topping_name") VALUES (1, 'Bacon'), (2, 'BBQ Sauce'), (3, 'Beef'), (4, 'Cheese'), (5, 'Chicken'), (6, 'Mushrooms'), (7, 'Onions'), (8, 'Pepperoni'), (9, 'Peppers'), (10, 'Salami'), (11, 'Tomatoes'), (12, 'Tomato Sauce');
Тематическое исследование разбито на 4 области фокуса,
A – Показатели пиццы
B – Runner & Customer Experience
C — оптимизация ингредиентов
D — Цены и рейтинги
Перед тем, как приступить к решению вопросов, данные приведены неверно или неаккуратно. Поэтому сначала нам нужно выполнить некоторые процедуры очистки данных, чтобы избавиться от них.
Сначала мы очищаем таблицу customer_orders
. Как мы видим, есть много пустых ячеек и некоторые неправильно введенные значения NULL. Поэтому мы исправим это, заменив «нулевые» и пустые ячейки значениями NULL.
DROP TABLE IF EXISTS customer_orders_2; CREATE TABLE customer_orders_2 AS SELECT * FROM customer_orders; UPDATE customer_orders_2 SET exclusions = (CASE WHEN exclusions ='' OR exclusions LIKE '%null%' THEN NULL ELSE exclusions END), extras = (CASE WHEN extras='' OR extras LIKE '%null%' OR extras IS NULL THEN NULL ELSE extras END);
Затем чистим runner_orders
. Имеются ошибочно введенные нулевые значения, пустые ячейки, а distance
и duration
будут стандартизированы в одну и ту же единицу измерения, и эта единица будет удалена из данных для возможности расчетов.
DROP TABLE IF EXISTS runner_orders_2; CREATE TABLE runner_orders_2 AS SELECT * FROM runner_orders; UPDATE runner_orders_2 SET pickup_time = (CASE WHEN pickup_time LIKE '%null%' THEN NULL ELSE pickup_time END), distance = (CASE WHEN distance LIKE '%null%' THEN NULL WHEN distance LIKE '%km' THEN REPLACE(distance, 'km', '') ELSE distance END), duration = (CASE WHEN duration LIKE '%null%' THEN NULL WHEN duration LIKE '%minutes%' THEN REPLACE(duration, 'minutes', '') WHEN duration LIKE '%mins%' THEN REPLACE(duration, 'mins', '') WHEN duration LIKE '%minute%' THEN REPLACE(duration, 'minute', '') ELSE duration END), cancellation = (CASE WHEN cancellation LIKE '%null' OR cancellation = '' THEN NULL ELSE cancellation END);
Решения:
(A) Показатели пиццы
[1]Сколько пицц было заказано?
SELECT COUNT(*) AS Pizzas_Ordered FROM customer_orders_2;
[2]Сколько уникальных заказов клиентов было сделано?
SELECT COUNT(DISTINCT customer_id) AS Orders_Made FROM customer_orders_2;
[3]Сколько успешных заказов было доставлено каждым бегуном?
SELECT runner_id,COUNT(order_id) FROM runner_orders_2 WHERE cancellation IS NULL GROUP BY runner_id;
[4]Сколько пицц каждого типа было доставлено?
SELECT customer_orders_2.pizza_id,pizza_names.pizza_name,COUNT(customer_orders_2.pizza_id) AS PizzaCount FROM customer_orders_2 JOIN runner_orders_2 ON customer_orders_2.order_id = runner_orders_2.order_id JOIN pizza_names ON customer_orders_2.pizza_id = pizza_names.pizza_id WHERE runner_orders_2.cancellation IS NULL GROUP BY pizza_id;
[5]Сколько вегетарианцев и любителей мяса заказал каждый покупатель?
SELECT c.customer_id, COUNT(CASE WHEN p.pizza_name = 'Vegetarian' then 1 END) AS Vegetarians, COUNT(CASE WHEN p.pizza_name = 'Meatlovers' then 1 END) AS Meatlovers FROM customer_orders_2 c JOIN pizza_names p ON c.pizza_id = p.pizza_id GROUP BY c.customer_id;
[6]Какое максимальное количество пицц было доставлено в одном заказе?
SELECT c.order_id,COUNT(c.pizza_id) AS No_of_Pizzas FROM customer_orders_2 c JOIN runner_orders_2 r ON c.order_id = r.order_id WHERE r.cancellation IS NULL GROUP BY c.order_id ORDER BY No_of_Pizzas DESC LIMIT 1;
[7]Для каждого клиента, сколько доставленных пицц было изменено хотя бы 1 раз, а сколько не было изменено?
SELECT c.customer_id, COUNT(CASE WHEN c.exclusions IS NOT NULL OR c.extras IS NOT NULL THEN 1 END) AS Changes, COUNT(CASE WHEN c.exclusions IS NULL AND c.extras IS NULL THEN 1 END) AS NoChanges FROM customer_orders_2 c JOIN runner_orders_2 r ON c.order_id = r.order_id WHERE r.cancellation IS NULL GROUP BY c.customer_id;
[8]Сколько пицц было доставлено с исключениями и дополнениями?
SELECT COUNT(CASE WHEN c.exclusions IS NOT NULL AND c.extras IS NOT NULL THEN 1 END) AS Changes FROM customer_orders_2 c JOIN runner_orders_2 r ON c.order_id = r.order_id WHERE r.cancellation IS NULL;
[9]Каков был общий объем пиццы, заказанной за каждый час дня?
SELECT HOUR(order_time) AS hour, COUNT(order_id) AS total_volume FROM customer_orders_2 GROUP BY HOUR(order_time);
[10]Каков был объем заказов в каждый день недели?
SELECT DAYNAME(order_time) AS day_of_week, COUNT(order_id) AS total_volume FROM customer_orders_2 GROUP BY DAYNAME(order_time) ORDER BY total_volume DESC;
(B) Runner и клиентский опыт
[1]Сколько бегунов зарегистрировалось на каждую неделю? (т. е. начало недели 2021-01-01
)
SELECT WEEK(registration_date) AS WeekNumber, COUNT(runner_id) AS NoOfRunners FROM runners GROUP BY WeekNumber;
[2]Сколько в среднем времени в минутах потребовалось каждому курьеру, чтобы добраться до штаб-квартиры Pizza Runner, чтобы забрать заказ?
SELECT r.runner_id, ROUND(AVG(TIMESTAMPDIFF(MINUTE,c.order_time,r.pickup_time)),1) AS Avg_Pickup_Time FROM customer_orders_2 c JOIN runner_orders_2 r ON c.order_id = r.order_id GROUP BY r.runner_id;
[3]Есть ли какая-либо связь между количеством пицц и тем, сколько времени требуется для приготовления заказа?
WITH pizza_prep AS( SELECT c.order_id,COUNT(c.order_id) AS NoOfPizza, ROUND(AVG(TIMESTAMPDIFF(MINUTE,c.order_time,r.pickup_time)),1) AS prep_time FROM customer_orders_2 c JOIN runner_orders_2 r USING(order_id) WHERE r.cancellation IS NULL GROUP BY c.order_id) SELECT NoOfPizza,prep_time FROM pizza_prep GROUP BY NoOfPizza;
[4]Есть ли какая-либо связь между количеством пицц и тем, сколько времени требуется для приготовления заказа?
SELECT c.customer_id, ROUND(AVG(r.distance),2) AS AvgDistance FROM customer_orders_2 c JOIN runner_orders_2 r USING(order_id) WHERE r.cancellation IS NULL AND r.distance IS NOT NULL GROUP BY c.customer_id;
[5]Какова разница между самым длинным и самым коротким временем доставки для всех заказов?
SELECT MAX(duration) - MIN(duration) AS DeliveryTimeRange FROM runner_orders_2;
[6]Какова была средняя скорость каждого бегуна при каждой доставке и замечаете ли вы какие-либо тенденции в этих значениях?
SELECT r.runner_id, ROUND((r.distance/(TIMESTAMPDIFF(MINUTE,c.order_time,r.pickup_time)))*60,2) AS Speed_kmph FROM customer_orders_2 c JOIN runner_orders_2 r USING(order_id) WHERE r.cancellation IS NULL AND r.pickup_time IS NOT NULL GROUP BY r.runner_id;
[7]Каков процент успешных показов для каждого бегуна?
SELECT runner_id, ROUND(((SUM(CASE WHEN cancellation IS NULL then 1 ELSE 0 END)/COUNT(order_id))*100),2) AS Success_Percent FROM runner_orders_2 GROUP BY runner_id;
(C) Оптимизация ингредиентов
[1]Каковы стандартные ингредиенты для каждой пиццы?
DROP TABLE if EXISTS pizza_recipes_mod; CREATE TABLE pizza_recipes_mod( pizza_id INTEGER, topping_id INTEGER); INSERT INTO pizza_recipes_mod VALUES (1,1), (1,2), (1,3), (1,4), (1,5), (1,6), (1,8), (1,10), (2,4), (2,6), (2,7), (2,9), (2,11), (2,12); SELECT p.pizza_id,n.pizza_name,GROUP_CONCAT(t.topping_name) AS Toppings FROM pizza_recipes_mod p JOIN pizza_toppings t USING(topping_id) JOIN pizza_names n USING(pizza_id) GROUP BY p.pizza_id;
[2]Что чаще всего добавляли?
WITH cte AS (SELECT substring_index(extras,',', 1) AS extras1 FROM customer_orders UNION ALL SELECT substring_index(extras,',', -1) FROM customer_orders) SELECT COUNT(topping_name), topping_name FROM cte JOIN pizza_toppings p ON p.topping_id = cte.extras1 GROUP BY topping_name ORDER BY COUNT(topping_name) DESC LIMIT 1;
[3]Что чаще всего исключали?
WITH cte AS (SELECT substring_index(exclusions,',', 1) AS exclusions FROM customer_orders UNION ALL SELECT substring_index(exclusions,',', -1) FROM customer_orders) SELECT COUNT(topping_name), topping_name FROM cte JOIN pizza_toppings p ON p.topping_id = cte.exclusions GROUP BY topping_name ORDER BY COUNT(topping_name) desc LIMIT 1;
(D) Цены и рейтинги
[1]Если пицца Meat Lovers стоит 12 долларов США, а вегетарианская – 10 долларов США, и плата за изменения не взималась, сколько денег заработала Pizza Runner, если не взимать плату за доставку?
SELECT SUM(CASE WHEN pizza_id = 1 then 12 when pizza_id = 2 then 10 END) AS Total_Earnings FROM runner_orders_2 JOIN customer_orders_2 USING(order_id) WHERE cancellation IS NULL;
[2]Что, если за пиццу взимается дополнительная плата в размере 1 доллара США? [Дополнительный сыр стоит 1 доллар]
WITH cte AS (SELECT (CASE WHEN pizza_id=1 THEN 12 WHEN pizza_id = 2 THEN 10 END) AS pizza_cost, c.exclusions, c.extras FROM runner_orders_2 r JOIN customer_orders_2 c ON c.order_id = r.order_id WHERE r.cancellation IS NULL) SELECT SUM(CASE WHEN extras IS NULL THEN pizza_cost WHEN LENGTH(extras) = 1 THEN pizza_cost + 1 ELSE pizza_cost + 2 END) AS Cost_with_Extras FROM cte;
[3]Команда Pizza Runner теперь хочет добавить дополнительную систему оценок, которая позволит клиентам оценивать своих бегунов. Как бы вы разработали дополнительную таблицу для этого нового набора данных — создайте схему для этой новой таблицы и вставьте свои собственные данные для оценок для каждого успешного заказа клиента от 1 до 5.
DROP TABLE IF EXISTS ratings; CREATE TABLE ratings (order_id INTEGER, rating INTEGER); INSERT INTO ratings (order_id ,rating) VALUES (1,3), (2,4), (3,5), (4,2), (5,1), (6,3), (7,4), (8,1), (9,3), (10,5); SELECT * FROM ratings;
[4]Используя только что созданную таблицу, можете ли вы объединить всю информацию вместе, чтобы сформировать таблицу, которая содержит следующую информацию для успешной доставки? [customer_id
, order_id
, runner_id
, rating
, order_time
, pickup_time
, время между заказом и получением, продолжительность доставки, средняя скорость, общее количество пицц]
SELECT c.customer_id, c.order_id, r.runner_id, rt.rating, c.order_time, r.pickup_time, TIMESTAMPDIFF(minute, order_time, pickup_time) as delivery_delay, r.duration, ROUND(avg(r.distance*60/r.duration),1) as avg_speed, count(c.pizza_id) as PizzaCount FROM customer_orders_2 c JOIN runner_orders_2 r ON c.order_id = r.order_id JOIN ratings rt ON rt.order_id = c.order_id GROUP BY c.customer_id, c.order_id, r.runner_id, rt.rating, c.order_time, r.pickup_time, delivery_delay, r.duration ORDER BY c.customer_id;
[5]Если пицца Meat Lovers стоила 12 долларов США, а вегетарианская – 10 долларов США по фиксированной цене без дополнительных расходов, а каждому бегуну платили 0,30 доллара США за пройденный километр, сколько денег осталось у Pizza Runner после этих доставок?
SELECT ROUND(SUM(CASE WHEN pizza_id=1 THEN 12 WHEN pizza_id = 2 THEN 10 END) - SUM((r.distance+0) * 0.3),2) AS pizza_cost, (SUM(r.distance+0) * 0.3) AS distance_cost, SUM(CASE WHEN pizza_id=1 THEN 12 WHEN pizza_id = 2 THEN 10 END ) AS Total_Cost FROM runner_orders_2 r JOIN customer_orders_2 c ON c.order_id = r.order_id WHERE r.cancellation IS NULL;
Заключение:
В заключение, эта статья обрисовала в общих чертах вторую неделю 8-недельного приключения SQL. Я узнал об объединениях, агрегировании, общих табличных выражениях (CTE), функциях времени и т. д., решая вопросы из тематического исследования на этой неделе.
Я также хотел бы поблагодарить вас за то, что вы прочитали этот вывод. Я надеюсь, что вы нашли его полезным и информативным. Если у вас есть какие-либо вопросы или комментарии, пожалуйста, не стесняйтесь оставлять их ниже.
Решения для Недели 3 этой задачи будут опубликованы в следующие выходные.
Ставьте Нравится, делитесь и подписывайтесь на меня на Medium, чтобы получать больше интересных статей на темы Data Analytics, Data Viz, AI & ML и т. д.