← Powrót do Portfolio
Analiza sprzedaży, rentowności i zapasów sieci Maven Toys — widok główny projektu

Analiza sprzedaży, rentowności i zapasów sieci Maven Toys

SQLMySQLDBeaverCTEFunkcje oknaAnaliza biznesowa

Kompleksowe studium przypadku SQL poświęcone analizie sprzedaży, rentowności, wyników sklepów, trendów oraz aktualnego poziomu zapasów fikcyjnej sieci Maven Toys działającej w Meksyku.

Przeanalizowałam ponad 829 tys. transakcji sprzedaży z okresu od stycznia 2022 roku do września 2023 roku. Celem projektu było przejście od surowych danych źródłowych do wniosków i rekomendacji wspierających decyzje dotyczące produktów, sklepów oraz zarządzania zapasami.

1Problem i cel projektu

Sieć posiadająca wiele sklepów i produktów potrzebuje nie tylko informacji o całkowitym przychodzie. Do podejmowania decyzji biznesowych konieczne jest również ustalenie:

  • które produkty i kategorie generują największy zysk,
  • jak zmieniają się wyniki w czasie,
  • które placówki osiągają najlepsze rezultaty,
  • czy poziom zapasów odpowiada bieżącej sprzedaży,
  • gdzie występuje ryzyko braku produktów,
  • gdzie środki są zamrożone w nadmiernym zapasie.

Przygotowałam analizę odpowiadającą na następujące pytania:

  • jaki jest łączny przychód, koszt produktów, zysk brutto na sprzedaży i marża brutto,
  • które kategorie i produkty osiągają najlepsze wyniki,
  • jak przychód zmienia się miesiąc do miesiąca i rok do roku,
  • czy pod koniec analizowanego okresu widoczne jest przyspieszenie lub osłabienie sprzedaży,
  • które sklepy i typy lokalizacji osiągają najlepsze wyniki,
  • jaka jest aktualna wartość zapasów,
  • które kombinacje sklep–produkt mają zbyt niski, optymalny lub nadmierny zapas,
  • gdzie występuje brak zapasu pomimo wcześniejszej sprzedaży,
  • gdzie zapas pozostaje w sklepie mimo braku sprzedaży.

Projekt przygotowałam jako kompletną analizę biznesową wykonaną w SQL, a nie tylko zbiór pojedynczych zapytań technicznych.

2Dane

Dane pochodzą z publicznego zbioru Maven Analytics i przedstawiają fikcyjną sieć sklepów z zabawkami działającą w Meksyku.

TabelaZawartośćLiczba rekordów
salesTransakcje sprzedaży produktów według sklepów829 262
productsNazwy, kategorie, koszty i ceny produktów35
storesSklepy, miasta, typy lokalizacji i daty otwarcia50
inventoryAktualny zapas według sklepu i produktu1 593

Dane sprzedażowe obejmują okres od 1 stycznia 2022 roku do 30 września 2023 roku.

Tabela inventory przedstawia aktualny stan zapasów. Umożliwia ocenę ich poziomu i struktury, ale nie pokazuje historycznych zmian stanów magazynowych.

Źródło danych: Mexico Toy Sales – Maven Analytics

3Struktura bazy danych

Główną tabelą transakcyjną jest tabela sales. Każdy rekord sprzedaży jest powiązany z konkretnym produktem i sklepem. Tabela inventory również łączy produkty ze sklepami, ale przedstawia aktualny stan magazynowy dla konkretnej kombinacji sklep–produkt.

W analizowanej bazie:

  • sale_id jest kluczem głównym tabeli sales,
  • product_id jest kluczem głównym tabeli products,
  • store_id jest kluczem głównym tabeli stores,
  • połączenie store_id + product_id tworzy klucz złożony tabeli inventory,
  • kolumny product_id i store_id pozwalają łączyć tabele podczas analizy.

W bazie źródłowej relacje nie były fizycznie zdefiniowane jako klucze obce. Na potrzeby dokumentacji utworzyłam w DBeaverze diagram ERD z relacjami wirtualnymi:

  • sales.product_id → products.product_id,
  • sales.store_id → stores.store_id,
  • inventory.product_id → products.product_id,
  • inventory.store_id → stores.store_id.

Widoku vw_products_clean nie umieściłam na diagramie, ponieważ utworzyłam go podczas przygotowania danych i nie należy on do czterech tabel źródłowych.

Diagram ERD bazy danych Maven Toys
Zdjęcie 1. Diagram ERD przedstawiający tabele źródłowe oraz relacje wykorzystywane w analizie.

4Kontrola jakości i przygotowanie danych

Przed rozpoczęciem właściwej analizy sprawdziłam jakość, kompletność i spójność danych.

Kontrola obejmowała:

  • liczbę rekordów w każdej tabeli,
  • brakujące wartości,
  • duplikaty kluczy głównych,
  • duplikaty klucza złożonego w tabeli inventory,
  • zgodność identyfikatorów produktów i sklepów pomiędzy tabelami,
  • zakres dat sprzedaży,
  • produkty, których cena była niższa lub równa kosztowi,
  • kompletność kombinacji sklep–produkt w tabeli zapasów.

Nie znalazłam brakujących wartości, zduplikowanych kluczy, niezgodnych identyfikatorów ani produktów sprzedawanych po cenie niższej lub równej kosztowi.

Koszty i ceny produktów były zapisane jako tekst zawierający symbol waluty. Utworzyłam widok vw_products_clean, w którym usunęłam symbol waluty i przekształciłam wartości do typu DECIMAL(10,2). W ten sposób przygotowałam jednolitą warstwę danych do dalszych obliczeń bez zmieniania tabeli źródłowej.

Dla 50 sklepów i 35 produktów istnieje 1 750 możliwych kombinacji sklep–produkt. Tabela inventory zawiera 1 593 rekordy, dlatego dla 157 kombinacji brakuje informacji o zapasie.

Nie potraktowałam brakujących rekordów jako zapasu równego zero, ponieważ dane nie wskazują, czy produkt nie jest oferowany w danym sklepie, czy odpowiedni rekord nie został zapisany.

5Zakres analizy i struktura skryptów SQL

Podzieliłam kod projektu na sześć tematycznych skryptów:

  1. 01_data_quality.sql – kontrola jakości, kompletności i spójności danych,
  2. 02_data_preparation.sql – przygotowanie widoku z oczyszczonymi kosztami i cenami produktów,
  3. 03_sales_and_profitability.sql – analiza sprzedaży, zysku, marży, kategorii i produktów,
  4. 04_time_analysis.sql – analiza miesiąc do miesiąca, rok do roku i średnich kroczących,
  5. 05_store_analysis.sql – wyniki i rankingi sklepów oraz typów lokalizacji,
  6. 06_inventory_analysis.sql – wartość zapasów, pokrycie sprzedaży zapasem i klasyfikacja pozycji magazynowych.

W zapytaniach wykorzystałam między innymi:

  • złączenia INNER JOIN i LEFT JOIN,
  • wspólne wyrażenia tabelowe CTE,
  • widok SQL,
  • funkcje agregujące,
  • instrukcję CASE,
  • funkcje COALESCE i NULLIF,
  • konwersję typów danych,
  • funkcje okna LAG i RANK,
  • średnie kroczące, rankingi w obrębie kategorii i porównania okresów.

6Wybrane rozwiązanie SQL – klasyfikacja zapasów

Jednym z najważniejszych elementów projektu było określenie poziomu zapasu dla każdej zapisanej kombinacji sklep–produkt.

Najpierw zagregowałam sprzedaż z ostatnich trzech miesięcy dostępnych w zbiorze, a następnie połączyłam ją z aktualnym stanem zapasu. Na tej podstawie obliczyłam średnią miesięczną sprzedaż oraz liczbę miesięcy, na które powinien wystarczyć obecny zapas.

stock_coverage_months_cte as
(
    select
        *
        ,round(units_sold_last_3_months / 3.0, 2)
            as avg_monthly_units_sold
        ,stock_on_hand
            / nullif(units_sold_last_3_months / 3.0, 0)
            as stock_coverage_months_exact
    from inventory_sales_cte
),
inventory_status_cte as
(
    select
        *
        ,case
            when stock_on_hand = 0 then 'Out of Stock'
            when units_sold_last_3_months = 0
                and stock_on_hand > 0 then 'No Sales'
            when stock_coverage_months_exact < 1 then 'Low Stock'
            when stock_coverage_months_exact <= 3 then 'Optimal Stock'
            else 'Overstock'
        end as inventory_status
    from stock_coverage_months_cte
)

Funkcja NULLIF zabezpiecza obliczenie przed dzieleniem przez zero. Za pomocą instrukcji CASE przypisałam każdej pozycji jeden z pięciu statusów: brak zapasu, niski zapas, zapas optymalny, nadmierny zapas lub brak sprzedaży.

Rozdzielenie kolejnych etapów pomiędzy CTE ułatwiło mi kontrolę obliczeń i pozwoliło oddzielić przygotowanie danych od właściwej klasyfikacji.

Pełne zapytanie znajduje się w repozytorium projektu.

7Najważniejsze wyniki

Wynik całej sieci

WskaźnikWynik
Przychód14 444 572,35 USD
Koszt produktów10 430 543,35 USD
Zysk brutto na sprzedaży4 014 029,00 USD
Marża brutto27,79%

Obliczyłam zysk brutto na sprzedaży jako różnicę pomiędzy przychodem a kosztem produktów. Dane nie zawierają kosztów operacyjnych sklepów, dlatego wynik nie oznacza zysku netto.

Rentowność kategorii

KategoriaPrzychódZysk bruttoMarża bruttoPoziom rentowności
Toys5 093 241,00 USD1 079 527,00 USD21,20%Niski
Electronics2 246 771,25 USD1 001 437,00 USD44,57%Wysoki
Art & Crafts2 705 364,26 USD753 354,00 USD27,85%Średni
Games2 226 836,27 USD673 993,00 USD30,27%Wysoki
Sports & Outdoors2 172 359,57 USD505 718,00 USD23,28%Niski

Kategoria Toys wygenerowała najwyższy przychód i zysk brutto, ale jednocześnie osiągnęła najniższą marżę – 21,20%.

Electronics wygenerowała 1 001 437,00 USD zysku brutto przy przychodzie wynoszącym 2 246 771,25 USD. Oznacza to wynik zbliżony do kategorii Toys przy przychodzie niższym o ponad połowę. Marża brutto Electronics wyniosła 44,57%.

Wyniki części kategorii były silnie skoncentrowane na pojedynczych produktach:

  • Colorbuds odpowiadał za 69,63% przychodu kategorii Electronics,
  • Lego Bricks odpowiadał za 46,90% przychodu kategorii Toys.

Trend sprzedaży

Przychód od stycznia do września 2023 roku był o 30,86% wyższy niż w analogicznym okresie 2022 roku. Pod koniec analizowanego okresu dynamika sprzedaży jednak osłabła.

MiesiącZmiana m/mRóżnica względem średniej z poprzednich 3 miesięcyZmiana r/r
Sierpień 2023−20,22%−159 778,80 USD+35,03%
Wrzesień 2023−0,41%−107 647,25 USD+12,35%

Dane kończą się we wrześniu 2023 roku, dlatego nie można jednoznacznie stwierdzić, czy osłabienie oznaczało trwałą zmianę trendu, czy było efektem sezonowym.

Wyniki sklepów i typów lokalizacji

Najwyższy przychód osiągnął sklep Maven Toys Ciudad de Mexico 2: 554 553,43 USD przychodu, 169 856,00 USD zysku brutto na sprzedaży i 30,63% marży brutto.

Typ lokalizacjiLiczba sklepówŁączny przychódŚredni przychód na sklepMarża brutto
Downtown298 219 596,27 USD283 434,35 USD27,36%
Commercial123 279 139,52 USD273 261,63 USD28,27%
Residential61 656 113,98 USD276 019,00 USD27,80%
Airport31 289 722,58 USD429 907,53 USD29,31%

Sklepy typu Downtown wygenerowały najwyższy łączny przychód, ale stanowią większość sieci – 29 z 50 placówek.

Najwyższy średni przychód na jeden sklep osiągnęły lokalizacje Airport – 429 907,53 USD, czyli o 48,81% więcej od średniej dla całej sieci. Wynik ten dotyczy jednak tylko trzech sklepów, dlatego powinien być traktowany jako sygnał do dalszej analizy, a nie samodzielna podstawa decyzji o rozwoju tego typu lokalizacji.

Stan i struktura zapasów

Łączna wartość aktualnego zapasu według kosztu produktów wyniosła 300 209,58 USD.

Status zapasuKombinacje sklep–produktUdział kombinacjiWartość zapasuUdział wartości
Brak zapasu774,83%0,00 USD0,00%
Niski zapas93758,82%152 849,22 USD50,91%
Brak sprzedaży895,59%16 338,81 USD5,44%
Nadmierny zapas1237,72%32 630,96 USD10,87%
Optymalny zapas36723,04%98 390,59 USD32,77%

Tylko 23,04% zapisanych kombinacji sklep–produkt miało optymalny poziom zapasu. Kolejne 58,82% miało zapas wystarczający na mniej niż jeden miesiąc, a w przypadku 77 kombinacji zapas wynosił zero pomimo sprzedaży w poprzednich trzech miesiącach.

Nadmierny zapas oraz zapas produktów bez sprzedaży miały łączną wartość 48 969,77 USD, co odpowiadało 16,31% wartości całego zapasu.

8Wnioski i rekomendacje biznesowe

Analiza pokazała jednoczesne występowanie dwóch przeciwstawnych problemów: ryzyka utraty sprzedaży z powodu braków i niskich stanów oraz zamrożenia środków w nadmiernym zapasie i produktach bez sprzedaży.

Na podstawie wyników analizy rekomendowałabym:

  • przeanalizowanie w pierwszej kolejności pozycji bez zapasu i z niskim zapasem,
  • ustalanie priorytetów uzupełnienia na podstawie tempa sprzedaży oraz zysku jednostkowego,
  • sprawdzenie możliwości przesunięcia produktów pomiędzy sklepami,
  • ograniczenie kolejnych zamówień lub zastosowanie działań promocyjnych dla produktów z nadmiernym zapasem i bez sprzedaży,
  • ochronę dostępności produktów Colorbuds i Lego Bricks ze względu na ich duży udział w wynikach całych kategorii,
  • analizę cen, struktury asortymentu i kosztów zakupu w kategoriach o niskiej marży,
  • dalsze monitorowanie sprzedaży po wrześniu 2023 roku przed zmianą prognoz lub planów zakupowych,
  • porównanie asortymentu, natężenia ruchu i kosztów działania sklepów Airport przed podjęciem decyzji o rozwoju tego typu lokalizacji.

9Założenia i ograniczenia analizy

Określiłam poziomy rentowności na podstawie marży brutto:

  • wysoki – marża brutto co najmniej 30%,
  • średni – marża brutto od 25% do 29,99%,
  • niski – marża brutto poniżej 25%.

Progi te są założeniem analitycznym przyjętym na potrzeby projektu, a nie standardem branżowym.

Obliczyłam zysk brutto na sprzedaży jako różnicę pomiędzy przychodem a kosztem produktów. Zbiór nie zawiera kosztów operacyjnych sklepów.

Obliczyłam pokrycie zapasu na podstawie średniej miesięcznej sprzedaży z okresu od lipca do września 2023 roku.

Zdefiniowałam statusy zapasów następująco:

  • niski zapas – poniżej jednego miesiąca sprzedaży,
  • zapas optymalny – od jednego do trzech miesięcy sprzedaży,
  • nadmierny zapas – powyżej trzech miesięcy sprzedaży.

Tabela inventory przedstawia aktualny stan zapasów. Nie umożliwia analizy ich historycznych poziomów ani dokładnego określenia wartości sprzedaży utraconej z powodu braku produktu.

Nie potraktowałam brakujących rekordów w tabeli zapasów jako zerowego zapasu.

Dane sprzedażowe kończą się we wrześniu 2023 roku, dlatego ocena późniejszego trendu wymagałaby danych z kolejnych miesięcy.

10Dalszy rozwój projektu

Projekt mogłabym rozszerzyć o:

  • dashboard prezentujący najważniejsze wyniki,
  • dane historyczne dotyczące poziomu zapasów,
  • koszty operacyjne poszczególnych sklepów,
  • analizę rotacji zapasów,
  • prognozowanie zapotrzebowania,
  • wyznaczenie poziomów zapasu uruchamiających nowe zamówienie po uzupełnieniu danych o czas dostawy i zapas bezpieczeństwa,
  • automatyczne alerty dla produktów z niskim lub nadmiernym zapasem,
  • bardziej szczegółową analizę wpływu daty otwarcia sklepu i typu lokalizacji na wyniki.

11Czego się nauczyłam

Podczas pracy nad projektem przećwiczyłam projektowanie kompletnej analizy SQL, w której kolejne zapytania prowadzą od kontroli danych do wniosków biznesowych.

Rozwinęłam umiejętności obejmujące:

  • sprawdzanie jakości, kompletności i spójności danych,
  • pracę z kilkoma powiązanymi tabelami,
  • rozpoznawanie kluczy głównych, obcych i złożonych,
  • przygotowanie widoku z oczyszczonymi danymi,
  • budowę wieloetapowych zapytań z użyciem CTE,
  • wykorzystanie funkcji okna do rankingów i porównań okresów,
  • obliczanie średnich kroczących,
  • zabezpieczanie obliczeń przy użyciu NULLIF i COALESCE,
  • tworzenie klasyfikacji za pomocą CASE,
  • definiowanie założeń i ograniczeń analizy,
  • interpretowanie wyników technicznych w kontekście biznesowym,
  • formułowanie rekomendacji bez wykraczania poza zakres dostępnych danych,
  • uporządkowanie projektu i opublikowanie go w repozytorium GitHub.

Projekt pokazał mi, że dobra analiza SQL nie kończy się na otrzymaniu poprawnego wyniku zapytania. Równie ważne jest sprawdzenie jakości danych, opisanie przyjętej logiki, wskazanie ograniczeń oraz przełożenie wyników na informacje przydatne dla biznesu.

12Repozytorium

Repozytorium zawiera:

  • cztery źródłowe pliki CSV,
  • sześć uporządkowanych tematycznie skryptów SQL,
  • szesnaście wyeksportowanych wyników zapytań,
  • diagram ERD,
  • opis projektu, założeń analitycznych, wyników i rekomendacji.

Zobacz na GitHubie