Пекарната, етап 3: въпросите
Накратко: Третият етап отговаря на въпросите на пекарната с JOIN, GROUP BY, LEFT JOIN, WITH и COALESCE: менюто, сметките, най-продаваното и приходите по град.
Проблем
Какво искат да знаят
Пекарната има данни. Сега иска отговори: какво да напише в менюто, колко е платил всеки клиент, кое да пече повече, на кого да прати отстъпка, кои поръчки са големи и откъде идват парите.
Всеки въпрос е една заявка към базата от етап 2. Нищо не се записва: базата е същата след всяка заявка.
Идеята
Евтините неща
Най-простият въпрос е към една таблица: продуктите под 2 €, от най-евтиния нагоре.
Пробвай
Менюто
Направи менюто: категорията (колона category), името и цената на всеки продукт. Подреди го по категории в реда на номерата им (хляб, сладкиши, напитки), а в категорията от най-евтиния продукт.
Решение
SELECT c.name AS category, p.name, p.price
FROM products p
JOIN categories c ON c.id = p.category_id
ORDER BY c.id, p.price;JOIN categories c ON c.id = p.category_id и ORDER BY c.id, p.price. С ORDER BY c.name категориите излизат по азбучен ред, а не в реда на менюто. С p.price DESC в категорията първо е най-скъпото.
Идеята
Сметката
Всеки ред от поръчката струва quantity * unit_price. Поръчка 5 на Десислава:
Сумата на поръчката е сборът на тези редове: SUM(quantity * unit_price) с GROUP BY по поръчка.
Пробвай
Сметките на всички
За всяка поръчка, която не е отказана: номер, името на клиента, датата и сумата (колона total, закръглена до 2 знака). Подреди по номер.
Решение
SELECT o.id, c.name, o.ordered_at, ROUND(SUM(i.quantity * i.unit_price), 2) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items i ON i.order_id = o.id
WHERE o.status <> 'cancelled'
GROUP BY o.id
ORDER BY o.id;Три таблици: orders, customers и order_items, WHERE o.status <> 'cancelled', GROUP BY o.id и ROUND(SUM(i.quantity * i.unit_price), 2). Без условието излиза и отказаната поръчка 4 за 15.60 €. С SUM(i.unit_price) трите кроасана се броят като един.
По-точно, за любопитните
GROUP BY o.id, а в SELECT са c.name и o.ordered_at, които не са в групата. SQLite го позволява. PostgreSQL приема o.ordered_at, защото първичният ключ на orders е в групата, но не и c.name, която е от друга таблица. Там се пише GROUP BY o.id, c.name, и така заявката работи навсякъде.
Пробвай
Кое да пече повече
Трите най-продавани продукта по бройки, без отказаните поръчки: името и броят (колона pieces), от най-продавания. При равни бройки по име.
Решение
SELECT p.name, SUM(i.quantity) AS pieces
FROM order_items i
JOIN orders o ON o.id = i.order_id
JOIN products p ON p.id = i.product_id
WHERE o.status <> 'cancelled'
GROUP BY p.id
ORDER BY pieces DESC, p.name
LIMIT 3;SUM(i.quantity), GROUP BY p.id, ORDER BY pieces DESC, p.name и LIMIT 3. С COUNT(*) се броят редовете, а не бройките: шестте кроасана на Десислава стават един. С отказаната поръчка баклавата влиза в челото с 6 бройки, които никой не е взел.
Идеята
Кой не е поръчвал
LEFT JOIN пази всеки клиент, дори без поръчка. Там колоните на поръчката са NULL, и WHERE o.id IS NULL оставя точно тях:
Пробвай
Отстъпка за новите
Пекарната праща отстъпка на всички, които нямат нито една истинска поръчка: без поръчки или само с отказани. Покажи имената им по азбучен ред.
Решение
SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status <> 'cancelled'
WHERE o.id IS NULL
ORDER BY c.name;Условието за статуса е в ON: LEFT JOIN orders o ON o.customer_id = c.id AND o.status <> 'cancelled'. Тогава отказаната поръчка на Борис не се свързва и той остава с NULL. Ако условието е в WHERE, редовете с NULL отпадат (NULL <> 'cancelled' не е истина) и не остава никой.
Пробвай
Големите поръчки
Кои поръчки (без отказаните) са над средната сума на поръчка? Номер и сума (total, закръглена до 2 знака), по номер. Направи сумите с WITH totals AS (…).
Решение
WITH totals AS (
SELECT o.id, SUM(i.quantity * i.unit_price) AS total
FROM orders o
JOIN order_items i ON i.order_id = o.id
WHERE o.status <> 'cancelled'
GROUP BY o.id
)
SELECT id, ROUND(total, 2) AS total
FROM totals
WHERE total > (SELECT AVG(total) FROM totals)
ORDER BY id;WITH totals AS (SELECT o.id, SUM(…) AS total … WHERE o.status <> 'cancelled' GROUP BY o.id), после WHERE total > (SELECT AVG(total) FROM totals). Средното е 9.40 €. С отказаната поръчка то става 10.43 € и излизат други поръчки. AVG(quantity * unit_price) от order_items е средното на ред (5.22 €), а не на поръчка.
Упражнение
Откъде идват парите
Приходите по град от поръчките, които не са отказани, за всички градове на клиентите. Град без приходи е с 0, а клиентите без град са под 'без град'. Колони city и revenue (закръглена до 2 знака), от най-големия приход.
Решение
SELECT COALESCE(c.city, 'без град') AS city,
ROUND(COALESCE(SUM(i.quantity * i.unit_price), 0), 2) AS revenue
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status <> 'cancelled'
LEFT JOIN order_items i ON i.order_id = o.id
GROUP BY c.city
ORDER BY revenue DESC;Два LEFT JOIN от customers, статусът е в ON, GROUP BY c.city, COALESCE(c.city, 'без град') и COALESCE(SUM(…), 0). С обикновен JOIN градът без поръчки изчезва. Без условието в ON София получава и отказаната поръчка на Борис.
По-точно, за любопитните
SUM върху група само с NULL дава NULL, не 0. Затова е COALESCE(SUM(…), 0). Сумата на поръчка е десетична, затова е ROUND: 0.1 + 0.2 в REAL не е точно 0.3.
Идеята
Какво следва
Всички тези заявки само четат. Следващият етап сменя данните: цени, статуси, наличности, изтрити поръчки.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: LEFT JOIN: и редовете без двойка · Много таблици и таблица със себе си · Подзаявки · Отчет на стъпки: WITH
- Задачи по темата: Проект: пекарната от нула