Финальный проект: мини-отчётность TechStore
Курс MS SQL с нуля · Финальный проект · Итог частей 1–4
Шесть блоков задач объединяют весь курс: от проектирования таблицы регламента до первого индекса с обоснованием. Пакет сдачи — deploy.sql, verify.sql и NOTES.md.
Финальная приёмка — на чистой базе: перед ней TechStore пересоздаётся скриптами урока 2, затем deploy.sql и verify.sql играются без правок.
Содержание
1. Правила сдачи
Сдаётся пакет из трёх файлов:
- deploy.sql — идемпотентный: повторное выполнение не меняет результат (CREATE OR ALTER, IF OBJECT_ID IS NOT NULL DROP перед CREATE TABLE);
- verify.sql — проверки всех критериев приёмки запросами с ожидаемыми значениями в комментариях;
- NOTES.md — заметки о компромиссах: выбранный индекс блока 6, сознательно оставленные ограничивающие факторы, ожидания от продвинутого уровня.
Тема проекта
операционная мини-отчётность
Имитация реальной задачи: отделу продаж нужен ежедневный пакет отчётов по учебной базе: витрина каталога, продажи по месяцам и городам, клиенты без заказов и товары без продаж, переезд клиента, десятка крупных клиентов с журналом запусков каждого отчёта.
2. Блоки 1 и 2: регламент и витрина
Блок 1. Регламент отчётов (урок 8)
Таблица dbo.ReportLog: номер записи IDENTITY, имя отчёта NVARCHAR NOT NULL, момент запуска DATETIME2(0) с умолчанием SYSDATETIME, число строк результата INT NOT NULL с CHECK на неотрицательность.
Чистый код
IF OBJECT_ID('dbo.ReportLog', 'U') IS NOT NULL DROP TABLE dbo.ReportLog;
GO
CREATE TABLE dbo.ReportLog
(
LogID INT IDENTITY(1,1) NOT NULL,
ReportName NVARCHAR(100) NOT NULL,
RunAt DATETIME2(0) NOT NULL DEFAULT SYSDATETIME(),
RowsCnt INT NOT NULL,
CONSTRAINT PK_ReportLog PRIMARY KEY (LogID),
CONSTRAINT CK_ReportLog_Rows CHECK (RowsCnt BETWEEN 0 AND 999999999)
);
GO
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
IF OBJECT_ID('dbo.ReportLog', 'U') IS NOT NULL DROP TABLE dbo.ReportLog; -- → таблица журнала пересоздаётся: шаг идемпотентен GO CREATE TABLE dbo.ReportLog ( LogID INT IDENTITY(1,1) NOT NULL, -- → автошаговый ключ записи ReportName NVARCHAR(100) NOT NULL, RunAt DATETIME2(0) NOT NULL DEFAULT SYSDATETIME(), -- → момент запуска ставится по умолчанию RowsCnt INT NOT NULL, CONSTRAINT PK_ReportLog PRIMARY KEY (LogID), CONSTRAINT CK_ReportLog_Rows CHECK (RowsCnt BETWEEN 0 AND 999999999) -- → коридор количества строк как коридор здравого смысла ); GOSQL
Приёмка: отрицательное число строк отклоняется Msg 547 с именем CK_ReportLog_Rows в тексте ошибки.
Блок 2. Каталог для менеджера (уроки 2–4)
Представление-витрина: активные товары с именами обеих категорий; число строк выдачи равно числу активных товаров COUNT-запросом, NULL допустим только в родительской категории корня.
Контракт витрины
шесть колонок
Название и цена товара, остаток StockQty, категория и её родитель, артикул SKU. Сортировка принадлежит запросу-потребителю: представление порядка не обещает.
3. Блоки 3 и 4: аналитика и сироты
Блок 3. Продажи по месяцам и городам (урок 5)
Два запроса: разрез по месяцам за весь период данных (год и месяц в ключе группы вместе) и средний чек по городам с порогом от ста заказов в HAVING.
Чистый код
SELECT YEAR(o.OrderDate) AS order_year, MONTH(o.OrderDate) AS order_month,
COUNT(*) AS orders_cnt, SUM(o.TotalAmount) AS revenue,
CAST(SUM(o.TotalAmount) * 1.0 / COUNT(*) AS DECIMAL(18,2)) AS avg_check
FROM dbo.Orders AS o
GROUP BY YEAR(o.OrderDate), MONTH(o.OrderDate)
ORDER BY order_year, order_month;
GO
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
SELECT YEAR(o.OrderDate) AS order_year, MONTH(o.OrderDate) AS order_month, -- → ключ периода: год и месяц вместе COUNT(*) AS orders_cnt, SUM(o.TotalAmount) AS revenue, -- → объёмы периода CAST(SUM(o.TotalAmount) * 1.0 / COUNT(*) AS DECIMAL(18,2)) AS avg_check -- → средний чек: умножением на дробную единицу деление выводится из целых FROM dbo.Orders AS o GROUP BY YEAR(o.OrderDate), MONTH(o.OrderDate) ORDER BY order_year, order_month; GOSQL
Блок 4. Забытые клиенты и залежавшиеся товары (урок 6)
Клиенты без единственного заказа — через NOT EXISTS; товары без единственной продажи — через EXCEPT. Встречная проверка чисел разностью счётчиков; в NOTES.md объясняется, почему NOT IN не применён.
Чистый код
SELECT p.ProductID FROM dbo.Products AS p
EXCEPT
SELECT oi.ProductID FROM dbo.OrderItems AS oi;
GO
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
SELECT p.ProductID FROM dbo.Products AS p EXCEPT SELECT oi.ProductID FROM dbo.OrderItems AS oi; -- → разность множеств: товары каталога, которых нет в продажах GOSQL
4. Блоки 5 и 6: переезд и учёт запусков
Блок 5. Переезд клиента (уроки 7 и 11)
Клиент с наименьшим номером переезжает в Санкт-Петербург паттерном «прогноз → транзакция → проверка @@ROWCOUNT → COMMIT или ROLLBACK с THROW»:
Чистый код
BEGIN TRAN;
UPDATE c SET City = N'Санкт-Петербург'
OUTPUT deleted.CustomerID, deleted.City AS old_city, inserted.City AS new_city
FROM dbo.Customers AS c
WHERE c.CustomerID = (SELECT MIN(x.CustomerID) FROM dbo.Customers AS x);
IF @@ROWCOUNT = 1
COMMIT;
ELSE
BEGIN
ROLLBACK;
THROW 50001, 'Неожиданное число затронутых строк при переезде', 1;
END
GO
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
BEGIN TRAN; -- → изменение под ручным контролем UPDATE c SET City = N'Санкт-Петербург' OUTPUT deleted.CustomerID, deleted.City AS old_city, inserted.City AS new_city -- → осмотр пары «было-стало» в том же операторе FROM dbo.Customers AS c WHERE c.CustomerID = (SELECT MIN(x.CustomerID) FROM dbo.Customers AS x); -- → прицел на одну строку: приём с подзапросом уже освоен IF @@ROWCOUNT = 1 COMMIT; -- → ровно одна строка — приём приборного прибора: подтверждаем ELSE BEGIN ROLLBACK; THROW 50001, 'Неожиданное число затронутых строк при переезде', 1; -- → любое иное число — откат и сигнал тревоги своим кодом 50001 END GOSQL
Блок 6. Учёт запусков и скорость (уроки 6, 9, 12)
Отчёт «крупные клиенты» с классом CASE по шкале урока 9; каждый запуск каждого отчёта регистрируется в ReportLog числом возвращённых строк; скорость горячего предиката обосновывается logical reads, а решение об индексе с ценой записи пишется в NOTES.md.
5. Критерии уровней сдачи
| Уровень | Что засчитывается |
|---|---|
| Сдал | все шесть блоков, deploy и verify играются чисто, NOTES честен |
| Отлично | индекс блока 6 создан и обоснован числами до/после; повторный deploy без ошибок; без дублей регистраций одного запуска |
| Работа к защите | ответы: чем SUM чистой таблицы надёжнее суммы по соединению; что будет с отчётами при пустой Orders; чем грозит UNIQUE на Email при существующих дублях |
6. Что дальше
После защиты курс закончен: двенадцать уроков и проект пройдены, среда и база остались в рабочем состоянии. Дальше — продвинутый курс MS SQL (T-SQL): оконные функции и рекурсия, чтение планов и построение индексов, процедуры и триггеры, транзакции с конкурентностью, секционирование больших таблиц.
Переустанавливать ничего не нужно: продвинутый курс работает на этой же TechStore, а ваши представления и ReportLog к его заданиям отношения не имеют и мешать не будут.
Проверено: 23.10.2026
↑ К оглавлению