Opas

Kysynnän ja myynnin ennustaminen BigQuery ML:llä

Kysyntäennuste voi auttaa suunnittelemaan hankintoja, henkilöstöä ja kapasiteettia. BigQuery ML:llä yrityksen myyntihistoriasta voidaan rakentaa ennuste samassa ympäristössä, jossa tiedot jo palvelevat raportointia. Tässä oppaassa etenemme myynnin määrittelystä usean tuoteryhmän SQL-esimerkkiin, ennusteen arviointiin ja käytännön suunnittelupäätökseen.

Julkaistu 2026-09-19 · Lukuaika 12 min

Kirjoittaja: · Data-analytiikan ja raportoinnin toteutuksia vuodesta 2018.

Aihe: Koneoppiminen

Oppaan ydinkohdat

  • Myynti kuvaa toteutuneita ostoja. Se ei yksin paljasta kysyntää, joka jäi toteutumatta esimerkiksi saatavuusongelman vuoksi.
  • Ennuste arvioidaan myöhemmällä ajanjaksolla ja sitä verrataan yksinkertaiseen lähtötasoon, kuten viimeiseen tunnettuun viikkoon.
  • Käyttäjä tarvitsee ennusteen lisäksi epävarmuuden, päivitysajankohdan ja tiedon siitä, mihin päätökseen ennustetta voi käyttää.

Mitä ennustetaan ja minkä päätöksen tueksi?

Aloittakaa päätöksestä, joka pitää tehdä ennen toteuman selviämistä. Hankinta tarvitsee arvion toimitusajan yli, työvuorosuunnittelu tulevien viikkojen kuormasta ja talous kuukausien myynnistä. Näihin ei välttämättä sovi sama aikajänne tai sama tarkkuustaso. Sopikaa myös, miten paljon liian pieni ja liian suuri arvio haittaavat toimintaa.

Valitkaa ennustettava suure täsmällisesti: tilatut kappaleet, toimitetut kappaleet, tilaukset vai myynti euroissa. Tilaushetki ja toimituspäivä kuvaavat eri asioita. Euromääräiseen myyntiin vaikuttavat määrän lisäksi hinnat, alennukset, palautukset ja valuutat. Käyttäkää samaa määritelmää lähdedatassa, arvioinnissa ja raportilla.

Ensimmäinen pilotti kannattaa rajata muutamaan riittävän säännölliseen tuoteryhmään tai palveluun. Vähän myyvien tuotteiden satunnaisia ostoja voi olla järkevämpää tarkastella viikkotasolla tai ryhmänä. Sopiva rajaus arvioidaan oman toiminnan perusteella.

Myyntihistoria ei aina kerro koko kysyntää

Jos tuotetta ei ole ollut saatavilla, pieni myyntimäärä voi johtua varaston loppumisesta. Pelkistä myyntiluvuista opetettu malli oppii tällöin toteutuneen myynnin tason. Se ei automaattisesti arvioi menetettyjä tilauksia. Liittäkää tarkasteluun saatavuus, sulkupäivät, valikoimamuutokset ja tunnetut kampanjat.

Erottakaa aito nollamyynti puuttuvasta aineistosta. Vahvistettu päivä ilman ostoja voi saada arvon nolla. Katkennut tiedonsiirto jätetään puutteeksi ja korjataan ennen mallintamista. Älkää täyttäkö kaikkia puuttuvia päiviä nolliksi vain saadaksenne yhtenäisen sarjan.

Tämän esimerkin kohteena on kirjattu kappalemyynti valituissa tuoteryhmissä, joiden historia ja saatavuus on tarkistettu. Se ei sisällä menetetyn kysynnän arviointia. Palautukset käsitellään erikseen, joten esimerkin kappalemäärät ovat kokonaislukuja ja vähintään nolla.

Valmistelkaa päiväkohtainen lähtötaulu

Oletamme valmiiksi rakennetun taulun oma-projekti.analytics.sales_daily. Siinä on yksi rivi tuoteryhmää ja paikallista myyntipäivää kohti. Kentät ovat series_id (STRING), sales_date (DATE), units (INT64) ja is_complete (BOOL). Viimeinen kenttä kertoo, että lähteen kyseisen päivän lataus on tarkistettu valmiiksi; se ei yksin vahvista datan liiketoiminnallista oikeellisuutta.

Rajataan harjoitus kahteen ennalta valittuun sarjaan, joiden tunnisteet ovat ryhma_a ja ryhma_b. Vaihtakaa ne, projektitunnus ja päivät omaan aineistoonne. Luokaa ml_demo-dataset lähteen kanssa samaan BigQuery-sijaintiin ja varmistakaa laskutus sekä tarvittavat käyttöoikeudet. CREATE OR REPLACE korvaa samannimisen taulun tai mallin, joten käyttäkää erillistä kokeiluympäristöä.

Jäädytämme historian ajalta 1.8.2025–31.8.2026. Tarkistukset vaativat kummaltakin sarjalta yhden hyväksytyn rivin joka päivälle. Vuoden mittainen historia on tämän harjoituksen rajaus, ei yleinen vähimmäisvaatimus tai lupaus riittävästä ennustetarkkuudesta.

Aineiston jäädytys ja päiväkohtaisen kattavuuden tarkistus
CREATE OR REPLACE TABLE `oma-projekti.ml_demo.sales_daily_snapshot` AS
SELECT series_id, sales_date, units, is_complete
FROM `oma-projekti.analytics.sales_daily`
WHERE sales_date BETWEEN DATE '2025-08-01' AND DATE '2026-08-31'
  AND series_id IN ('ryhma_a', 'ryhma_b');

ASSERT (
  SELECT COUNT(DISTINCT series_id) = 2
  FROM `oma-projekti.ml_demo.sales_daily_snapshot`
) AS 'Molemmat valitut sarjat tarvitaan';

ASSERT (
  SELECT COUNTIF(units IS NULL OR units < 0
    OR NOT COALESCE(is_complete, FALSE)) = 0
  FROM `oma-projekti.ml_demo.sales_daily_snapshot`
) AS 'Tarkista maarat ja latauksen valmius';

ASSERT (
  SELECT COUNT(*) = 0 FROM (
    SELECT series_id
    FROM `oma-projekti.ml_demo.sales_daily_snapshot`
    GROUP BY series_id
    HAVING COUNT(*) != DATE_DIFF(DATE '2026-08-31', DATE '2025-08-01', DAY) + 1
       OR COUNT(DISTINCT sales_date) != COUNT(*)
  )
) AS 'Jokaiselle sarjalle tarvitaan yksi rivi joka paivalle';

Opettakaa kokeilumalli ennen testijaksoa

Jätämme viimeiset 28 päivää, 4.–31.8.2026, arviointiin. Mallille annetaan vain sitä edeltävä historia. ARIMA_PLUS käyttää tässä kunkin sarjan aiempia arvoja; sarjat erotellaan TIME_SERIES_ID_COL-asetuksella. Kampanjabudjetti tai tuleva hinnanmuutos ei ole tämän mallin syöte.

Koodi luo kaksi erillistä aikasarjaennustetta samaan BigQuery ML -malliin. Arvioikaa tulokset sarjoittain: hyvä yhteistulos voi peittää yhden tärkeän tuoteryhmän suuren virheen. Säilyttäkää myös kuvaus siitä, mitä lähteen korjauksia oli saatavilla aineistoa jäädytettäessä.

Kokeilumalli: 28 päivän testijakso ei kuulu opetukseen
CREATE OR REPLACE MODEL `oma-projekti.ml_demo.sales_arima_backtest`
OPTIONS (
  MODEL_TYPE = 'ARIMA_PLUS',
  TIME_SERIES_TIMESTAMP_COL = 'sales_date',
  TIME_SERIES_DATA_COL = 'units',
  TIME_SERIES_ID_COL = 'series_id',
  DATA_FREQUENCY = 'DAILY',
  HORIZON = 28,
  AUTO_ARIMA = TRUE,
  AUTO_ARIMA_MAX_ORDER = 2
) AS
SELECT series_id, sales_date, units
FROM `oma-projekti.ml_demo.sales_daily_snapshot`
WHERE sales_date <= DATE '2026-08-03';

Verratkaa ennustetta viimeiseen tunnettuun viikkoon

Vertailuennuste toistaa opetuksen viimeisen viikon 28.7.–3.8.2026 viikonpäiväkohtaiset myynnit koko testijaksolle. Se käyttää vain ennustushetkellä tunnettua tietoa. Jos neljän viikon ennustetta verrattaisiin aina seitsemän päivää aiempaan toteumaan, myöhemmille testipäiville päätyisi testijakson aikana selvinnyttä tietoa.

Alla MAE kertoo keskimääräisen absoluuttisen virheen kappaleina. WAPE suhteuttaa absoluuttisten virheiden summan toteutuneeseen määrään; nollasummalla tulos on NULL. Tarkistakaa, että forecast_days on 28 jokaiselle sarjalle. Puuttuvia ennusteita ei saa hyväksyä sen perusteella, että jäljelle jääneiden päivien keskiarvo näyttää hyvältä.

Ennusteen ja vertailutason virhe tuoteryhmittäin
CREATE OR REPLACE TABLE `oma-projekti.ml_demo.sales_backtest_forecast` AS
SELECT *
FROM ML.FORECAST(
  MODEL `oma-projekti.ml_demo.sales_arima_backtest`,
  STRUCT(28 AS horizon, 0.95 AS confidence_level)
);

SELECT
  a.series_id,
  COUNT(*) AS actual_days,
  COUNT(f.forecast_value) AS forecast_days,
  AVG(ABS(a.units - f.forecast_value)) AS model_mae,
  AVG(ABS(a.units - b.units)) AS baseline_mae,
  SAFE_DIVIDE(SUM(ABS(a.units - f.forecast_value)), SUM(a.units)) AS model_wape
FROM `oma-projekti.ml_demo.sales_daily_snapshot` AS a
LEFT JOIN `oma-projekti.ml_demo.sales_backtest_forecast` AS f
  ON a.series_id = f.series_id
  AND a.sales_date = DATE(f.forecast_timestamp)
LEFT JOIN `oma-projekti.ml_demo.sales_daily_snapshot` AS b
  ON a.series_id = b.series_id
  AND EXTRACT(DAYOFWEEK FROM a.sales_date) = EXTRACT(DAYOFWEEK FROM b.sales_date)
  AND b.sales_date BETWEEN DATE '2026-07-28' AND DATE '2026-08-03'
WHERE a.sales_date BETWEEN DATE '2026-08-04' AND DATE '2026-08-31'
GROUP BY a.series_id;

Tarkastelkaa myös yli- ja aliennustamista, kampanjapäiviä ja ennustevälien osuvuutta. Kappalemäärän mallista voi tulla negatiivisia arvoja. Jos tuotantokäyttö edellyttää niiden rajaamista, määritelkää käsittelysääntö ja arvioikaa samalla tavalla käsitelty ennuste jo testissä.

Yksi kuukausi on ensimmäinen tarkistus. Toistakaa arviointi useilla ajankohdilla ja säilyttäkää viimeinen jakso lopulliseen testiin, jos valitsette menetelmää tai asetuksia aiempien testitulosten perusteella. Jälkikäteen korjattu historia voi antaa optimistisemman kuvan kuin alun perin käytettävissä ollut aineisto.

Muodostakaa hyväksytystä mallista raportoitava ennuste

Kun arviointi ja rajaukset on hyväksytty, opetetaan uusi malli koko jäädytetyllä historialla. Se tuottaa tässä syyskuun 2026 ensimmäisten 28 päivän ennusteen. Mallin nimen päiväys ilmaisee historian päättymisen. Vaihtakaa päivät ja versiot yhdessä, kun siirrytte omaan aineistoon.

Tallennamme piste-ennusteen lisäksi päiväkohtaisen ennustevälin, historian viimeisen päivän ja ajankohdan, jolloin tulos kirjoitettiin tauluun. Muodostusajankohta ei ole sama kuin lähdeaineiston tuoreus. Ennusteväli kuvaa mallin epävarmuutta omilla oletuksillaan; se ei takaa toteuman pysymistä rajojen sisällä.

Uusi malli ja nykyinen ennustetaulu raportointia varten
CREATE OR REPLACE MODEL `oma-projekti.ml_demo.sales_arima_20260831`
OPTIONS (
  MODEL_TYPE = 'ARIMA_PLUS',
  TIME_SERIES_TIMESTAMP_COL = 'sales_date',
  TIME_SERIES_DATA_COL = 'units',
  TIME_SERIES_ID_COL = 'series_id',
  DATA_FREQUENCY = 'DAILY',
  HORIZON = 28,
  AUTO_ARIMA = TRUE,
  AUTO_ARIMA_MAX_ORDER = 2
) AS
SELECT series_id, sales_date, units
FROM `oma-projekti.ml_demo.sales_daily_snapshot`;

CREATE OR REPLACE TABLE `oma-projekti.ml_demo.sales_forecast_current` AS
SELECT
  series_id,
  DATE(forecast_timestamp) AS forecast_date,
  forecast_value AS forecast_units,
  prediction_interval_lower_bound AS lower_units,
  prediction_interval_upper_bound AS upper_units,
  confidence_level,
  DATE '2026-08-31' AS trained_through,
  CURRENT_TIMESTAMP() AS generated_at,
  'sales_arima_20260831' AS model_version
FROM ML.FORECAST(
  MODEL `oma-projekti.ml_demo.sales_arima_20260831`,
  STRUCT(28 AS horizon, 0.95 AS confidence_level)
);

Kampanjat, hinnat ja hankintapäätökset

Jos kysyntään vaikuttavat tunnetut kampanjat tai hinnat, seuraava kokeilu voi käyttää niitä lisämuuttujina. BigQuery ML:n ARIMA_PLUS_XREG on tähän yksi vaihtoehto. Myös tulevien ennustepäivien lisämuuttujat pitää tällöin toimittaa mallille. Toteutunutta kampanjamyyntiä ei voi käyttää tulevan kampanjan syötteenä.

Suunniteltu budjetti tai hinta on skenaario, joka voi muuttua. Tallentakaa oletukset ennusteen yhteyteen. Mallin arvio hinnan ja myynnin yhteydestä ei yksin kerro, paljonko hinnan muuttaminen aiheuttaisi lisämyyntiä.

Ennustetta ei kopioida suoraan ostotilaukseksi. Hankinta huomioi lisäksi nykyisen varaston, avoimet tilaukset, toimitusajan ja sovitun palvelutavoitteen. Päivittäisiä ennustevälien rajoja ei voi sellaisenaan summata koko hankintajakson vastaavaksi ennusteväliksi, koska päivien virheet voivat riippua toisistaan.

Ajastus ja liiketoimintahyödyn seuranta

Tuotannossa lähteen valmius tarkistetaan ennen laskentaa. Uusi data ei päivitä opetettua ARIMA_PLUS-mallia itsestään. Sovitaan mallin uudelleenopetus, ennustehorisontti ja se, miten uusi tulos hyväksytään raportille. Säilyttäkää aiemmat ennusteversiot ennen nykyisen taulun korvaamista, jotta arviointi ei perustu jälkikäteen muuttuneeseen ennusteeseen.

Seuratkaa teknisen toiminnan lisäksi ennustevirhettä ja suunnittelun tuloksia. Esimerkiksi saatavuus, kiireelliset lisähankinnat tai suunnitteluun käytetty aika voivat kertoa hyödystä. Vertailussa huomioidaan myös valikoiman, hintojen ja toimintatapojen muutokset.

Vectura Solutions Oy voi yhdistää myynti- ja saatavuustiedot, toteuttaa rajatun ennustekokeilun ja tuoda hyväksytyn tuloksen BI-raportointiin. Toimituksen sisältö ja onnistumisen arviointi sovitaan oman suunnittelutarpeenne perusteella.

Tarvitsetteko ennusteen hankinnan tai kapasiteetin suunnitteluun?

Vectura Solutions Oy auttaa rajaamaan ennustettavan tarpeen, tarkistamaan aineiston ja vertaamaan mallia nykyiseen toimintatapaan. Ensimmäinen keskustelu on maksuton.

Keskustellaan toteutuksesta

Keskustellaan seuraavasta kehitysaskeleesta

Kertokaa tavoitteistanne ja nykyisestä ympäristöstänne. Arvioimme yhdessä sopivan aloituskohdan ja yhteistyömallin. Ensimmäinen keskustelu on maksuton.