Перейти к содержимому

Финальный проект: мини-отчётность TechStore

Курс MS SQL с нуля · Финальный проект · Итог частей 1–4

Шесть блоков задач объединяют весь курс: от проектирования таблицы регламента до первого индекса с обоснованием. Пакет сдачи — deploy.sql, verify.sql и NOTES.md.

Финальная приёмка — на чистой базе: перед ней TechStore пересоздаётся скриптами урока 2, затем deploy.sql и verify.sql играются без правок.

Содержание

1. Правила сдачи

Сдаётся пакет из трёх файлов:

  1. deploy.sql — идемпотентный: повторное выполнение не меняет результат (CREATE OR ALTER, IF OBJECT_ID IS NOT NULL DROP перед CREATE TABLE);
  2. verify.sql — проверки всех критериев приёмки запросами с ожидаемыми значениями в комментариях;
  3. NOTES.md — заметки о компромиссах: выбранный индекс блока 6, сознательно оставленные ограничивающие факторы, ожидания от продвинутого уровня.
Идемпотентность: каждый объект пакета создаётся повторяемо: представления через CREATE OR ALTER, таблица журнала — через DROP по условию IF OBJECT_ID. Пакет дважды подряд обязан отрабатывать без ошибок.
Чистый прогон: финальная приёмка выполняется после пересоздания базы скриптами урока 2: deploy.sql и verify.sql играются без правок. Проверки, зависящие от следов экспериментов, не принимаются.

Тема проекта

операционная мини-отчётность

Имитация реальной задачи: отделу продаж нужен ежедневный пакет отчётов по учебной базе: витрина каталога, продажи по месяцам и городам, клиенты без заказов и товары без продаж, переезд клиента, десятка крупных клиентов с журналом запусков каждого отчёта.

↑ К оглавлению

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
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    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
    SQL

Приёмка: отрицательное число строк отклоняется 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
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    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
    SQL

Блок 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
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    SELECT p.ProductID FROM dbo.Products AS p
    EXCEPT
    SELECT oi.ProductID FROM dbo.OrderItems AS oi;
    -- → разность множеств: товары каталога, которых нет в продажах
    GO
    SQL
↑ К оглавлению

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
SQL
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    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
    GO
    SQL

Блок 6. Учёт запусков и скорость (уроки 6, 9, 12)

Отчёт «крупные клиенты» с классом CASE по шкале урока 9; каждый запуск каждого отчёта регистрируется в ReportLog числом возвращённых строк; скорость горячего предиката обосновывается logical reads, а решение об индексе с ценой записи пишется в NOTES.md.

Число строк — факт: в ReportLog пишется фактическое число строк отчёта из переменной или подзапроса, а не ожидаемое: журнал нужен для расследований «почему отчёт вчера был пуст».
↑ К оглавлению

5. Критерии уровней сдачи

УровеньЧто засчитывается
Сдалвсе шесть блоков, deploy и verify играются чисто, NOTES честен
Отличноиндекс блока 6 создан и обоснован числами до/после; повторный deploy без ошибок; без дублей регистраций одного запуска
Работа к защитеответы: чем SUM чистой таблицы надёжнее суммы по соединению; что будет с отчётами при пустой Orders; чем грозит UNIQUE на Email при существующих дублях
↑ К оглавлению

6. Что дальше

После защиты курс закончен: двенадцать уроков и проект пройдены, среда и база остались в рабочем состоянии. Дальше — продвинутый курс MS SQL (T-SQL): оконные функции и рекурсия, чтение планов и построение индексов, процедуры и триггеры, транзакции с конкурентностью, секционирование больших таблиц.

Переустанавливать ничего не нужно: продвинутый курс работает на этой же TechStore, а ваши представления и ReportLog к его заданиям отношения не имеют и мешать не будут.

Перед стартом старшего курса: пересоздайте базу скриптами урока 2 и зафиксируйте контрольные счётчики 500, 3000, 25000: эталоны продвинутого курса считают от исходных данных.

Проверено: 23.10.2026

↑ К оглавлению
Конспект:
Четверг, 22 октября 2026