---
title: "Time Series Forecasting in PostgreSQL: SQL vs Python"
description: "Time series forecasting methods compared: which run natively in PostgreSQL/SQL, which need Python, and how to choose, with working examples."
section: "Time-series basics"
published: 2026-07-06T14:28:56.702Z
updated: 2026-09-15T00:00:00.000Z
---

*Updated at Sep 15, 2026*

> **TimescaleDB is now Tiger Data.**

Time-series forecasting takes historical, time-indexed observations and uses them to estimate future values of a variable. It's predictive and forward-looking. Time-series analysis, by contrast, looks backward, examining patterns already present in past data. Forecasting methods span a wide range too: a 7-day moving average you can compute in a single SQL query sits at one end, and a zero-shot foundation model trained on billions of time points sits at the other.

Most time-series forecasting guides assume Python from line one. The imports come first, then the DataFrame setup, then statsmodels or Prophet. If your data already lives in PostgreSQL, you're left to figure out the connection layer on your own.

This guide takes a different approach. It maps what you can do directly in the database against what actually needs a Python layer, and it walks through the practical criteria for deciding where that line sits. Tiger Data builds on PostgreSQL, so there's a natural bias here worth naming upfront: the in-database examples use TimescaleDB hyperfunctions (prebuilt SQL functions for common time-series calculations) where they add value. That said, the SQL methods covered here work on standard PostgreSQL too, and the guide is honest about the cases where Python is clearly the better tool.

Two myths keep coming up in developer communities, and both shape real implementation decisions in ways worth correcting. The first says you need Python for any forecasting that counts as *"real"*. The second says ARIMA is the default model for time-series work. Neither holds up, and this guide tackles both directly.

If you want the conceptual groundwork first, trend, seasonality, and noise decomposition, the [<u>foundational overview</u>](https://www.tigerdata.com/blog/what-is-time-series-forecasting) covers those in depth. This guide picks up from there, focused on implementation.

Throughout, each method gets classified one of three ways: in-database (pure SQL), Python-required, or a reasonable SQL approximation with Python as the cleaner alternative. The aim is to help you choose deliberately, instead of defaulting to Python out of habit.

## **The in-database vs. Python decision**

The table below classifies methods by what they actually require, not what's theoretically possible. A recursive CTE can approximate exponential smoothing, but whether it should depends on dataset size, refresh requirements, and team familiarity.

| **Method** | **SQL native** | **Python required** | **When to use** |
| --- | --- | --- | --- |
| Simple moving average | Yes | No | Trend smoothing, dashboards, alerting baselines |
| Time-weighted average | Yes (`time_weight()`) | No | Irregular timestamps, IoT sensor data |
| Linear regression (trend extrapolation) | Yes (`regr_slope()`, `regr_intercept()`) | No | Short-horizon, near-linear trend projection |
| Exponential smoothing (simple) | SQL via recursive CTE | No, but slow at scale | When recent data should outweigh older observations |
| Holt-Winters (double/triple) | SQL approximation possible | Recommended for seasonal variants | Stable seasonal patterns. Use Python for seasonal tuning |
| ARIMA / SARIMA | No | Yes (statsmodels) | Strong autocorrelation structure, irregular seasonality |
| Prophet | No | Yes | Human-driven seasonality, trend changepoints, holiday effects |
| ML-based (LSTM, XGBoost) | No | Yes | Non-linear patterns, many input features |
| Foundation models (Chronos-2, TimesFM, MOIRAI-2) | No | Yes | Zero-shot forecasting. Emerging, not yet production-standard |

For most Tiger Data setups, PostgreSQL handles the source of truth and the query engine. Python steps in as the model layer for complex forecasting, then writes results back to Postgres for serving. That split plays to each layer's strengths. Postgres handles high-throughput ingestion, time-range queries, and serving results at scale. Python handles iterative model fitting and the statistical machinery SQL was never built for.

Moving averages, time-weighted averages, and linear regression-based trend extrapolation are all production-ready in SQL for monitoring, alerting, and short-horizon projections. What actually pushes you toward Python is iterative model fitting and parameter optimization, not forecasting in general.

For short-horizon, trend-following use cases, simpler methods often come close to ARIMA's accuracy with a fraction of the complexity. The broader community has shifted toward foundation models and tree-based ML for structured business data. ARIMA still earns its keep when the autocorrelation structure is strong, but it stopped being anyone's default years ago.

## **Time series forecasting methods in PostgreSQL vs. Python**

These methods are implementable entirely in SQL, without a Python process or external model server. All examples assume a table with a timestamp column and a numeric value column. `time_bucket()` requires TimescaleDB and `time_weight()` requires the TimescaleDB Toolkit extension (bundled on [<u>Tiger Cloud</u>](https://www.tigerdata.com/cloud)). `regr_slope()` and `regr_intercept()` are native PostgreSQL aggregates.

### **Simple moving average with time_bucket()**

A simple moving average smooths out short-term noise by averaging values over a rolling window, giving you a lag-adjusted trend line. For dashboards, alerting thresholds, and short-horizon projections on well-behaved series, this is often all you need.

time_bucket() groups your data into consistent hourly intervals. Once you've averaged down to one row per hour, a window spanning 168 of those hourly rows covers 7 days:

`WITH hourly AS (
  SELECT time_bucket('1 hour', recorded_at) AS bucket,
         AVG(value) AS hourly_avg
  FROM sensor_readings
  GROUP BY bucket
)
SELECT
  bucket,
  AVG(hourly_avg) OVER (
    ORDER BY bucket
    ROWS BETWEEN 167 PRECEDING AND CURRENT ROW
  ) AS moving_avg_7d
FROM hourly
ORDER BY bucket;`

Standard SQL window functions treat every row equally, no matter what the timestamps say. So if your data is irregular (missing hours, inconsistent reporting intervals), the window ends up covering the wrong span of time and the averages come out wrong. For irregular data, reach for `time_weight()` instead.

For a deeper implementation guide covering SMA variants and edge cases, see [<u>moving averages in SQL</u>](https://www.tigerdata.com/learn/moving-averages-time-series-sql).

### **Time-weighted averages for irregular timestamps**

Real sensor data often arrives with gaps or variable reporting intervals. A row-based window average double-counts dense periods and under-weights sparse ones. `time_weight()` weights each observation by the duration it covers, so a reading held for 10 minutes contributes proportionally more than one held for 30 seconds.

`SELECT
  time_bucket('1 hour', recorded_at) AS bucket,
  average(time_weight('Linear', recorded_at, value)) AS time_weighted_avg
FROM sensor_readings
GROUP BY bucket
ORDER BY bucket;`

Use this for IoT sensor data, financial tick data, and any series where data arrival rate is not constant.

**Note:** It's a TimescaleDB Toolkit function with no direct equivalent in standard PostgreSQL.

### **Linear regression for trend extrapolation**

`regr_slope()` and `regr_intercept()` are native PostgreSQL aggregate functions that fit a least-squares regression line over a time window. No extension required. Slope and intercept together project the trend forward in a single query with no external dependencies.

The query below computes a regression-based 24-hour-ahead forecast from the past 7 days of hourly data:

`SELECT
  regr_slope(value, EXTRACT(EPOCH FROM recorded_at)) AS slope,
  regr_intercept(value, EXTRACT(EPOCH FROM recorded_at)) AS intercept,
  regr_slope(value, EXTRACT(EPOCH FROM recorded_at))
    * EXTRACT(EPOCH FROM NOW() + INTERVAL '24 hours')
  + regr_intercept(value, EXTRACT(EPOCH FROM recorded_at)) AS forecast_24h
FROM sensor_readings
WHERE recorded_at > NOW() - INTERVAL '7 days';`

This approach assumes a linear trend, so it degrades quickly on seasonal or cyclical data and gets unreliable beyond short forecast horizons. Use it when the trend stays roughly linear over the recent window. For longer horizons or non-linear patterns, reach for the next tier of methods.

### **Exponential smoothing via recursive CTE**

Exponential smoothing assigns decreasing weights to older observations, controlled by a smoothing factor alpha (0 < alpha < 1). A higher alpha puts more weight on recent data, a lower alpha puts more weight on history. It's computationally simple and works well on trend-following series that don't have seasonal components.

`WITH ordered AS (
  SELECT recorded_at, value,
         row_number() OVER (ORDER BY recorded_at) AS rn
  FROM sensor_readings
)
-- Recurse over the row index (no ORDER BY / LIMIT allowed in the recursive term)
, ema AS (
  SELECT recorded_at, value, value AS ema_value, rn
  FROM ordered WHERE rn = 1
  UNION ALL
  SELECT o.recorded_at, o.value,
         0.2 * o.value + 0.8 * e.ema_value, o.rn
  FROM ordered o
  JOIN ema e ON o.rn = e.rn + 1
)
SELECT recorded_at, value, ema_value FROM ema
ORDER BY recorded_at;`

This example uses alpha = 0.2, a common starting point that favors smoothness over responsiveness. Adjust it based on how quickly you want the average to react to new values.

Recursive CTEs in PostgreSQL process rows one at a time, so they slow down on large datasets. For production use at scale, you've got two reasonable options: pre-compute with continuous aggregates, or move the EMA computation to a Python layer. Once the recursive CTE becomes a bottleneck, you've crossed into Python territory.

[<u>Holt-Winters</u>](https://otexts.com/fpp2/holt-winters.html) extends exponential smoothing with trend and seasonality components. A double-exponential version (Holt's linear method) can be approximated in SQL with two-pass recursive CTEs, but the triple-exponential seasonal variant needs iterative optimization, and that's where Python's statsmodels is the right tool. The SQL approximation works fine as a prototype or for low-volume use, but production seasonal forecasting belongs in Python.

### **Pre-computed trend inputs with continuous aggregates**

If your moving average or regression scans the full historical table on every query, performance degrades as data grows. A query that took 100ms against 30 days of data might take seconds against 18 months.

Continuous aggregates (TimescaleDB's version of a materialized view, refreshed incrementally instead of recomputed from scratch) pre-compute and refresh rolled-up statistics on a schedule, so forecasting queries hit the materialized view rather than the raw hypertable.

`CREATE MATERIALIZED VIEW hourly_sensor_averages
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', recorded_at) AS bucket,
  AVG(value) AS avg_value,
  COUNT(*) AS reading_count,
  STDDEV(value) AS stddev_value
FROM sensor_readings
GROUP BY bucket
WITH NO DATA;

SELECT add_continuous_aggregate_policy('hourly_sensor_averages',
  start_offset => INTERVAL '3 hours',
  end_offset   => INTERVAL '1 hour',
  schedule_interval => INTERVAL '5 minutes');`

Continuous aggregates refresh only the time buckets that changed, not the full dataset. A 5-minute refresh cycle on hourly buckets keeps your forecasting inputs within 5-10 minutes of current, without a full-table scan on every query.

TimescaleDB's [<u>hypercore engine</u>](https://www.tigerdata.com/docs/learn/columnar-storage/understand-hypercore) (available in TimescaleDB 2.18 and later) complements this pattern by automatically managing hot and cold chunk storage: recent chunks stay in row format for writes, while older chunks convert to columnar storage for analytical scans. Forecasting queries pulling from the continuous aggregate are already fast, and queries that need to scan older raw data get faster columnar reads from Hypercore with no manual configuration needed.

Because pre-aggregation happens incrementally, near-real-time forecasting inputs stay tractable without sacrificing query performance. For implementation detail and refresh policy options, see [<u>continuous aggregates in TimescaleDB</u>](https://www.tigerdata.com/learn/continuous-aggregates-timescaledb).

## **When Python is the right layer for ARIMA, Prophet, and beyond**

Python is the right layer when the model needs iterative fitting (repeatedly adjusting parameters until the model converges), parameter optimization (searching for the settings that best fit your data), or non-linear pattern capture, things SQL was never designed to handle.

All methods in this section use PostgreSQL as the data source. Connect via psycopg2 or SQLAlchemy, pull pre-aggregated data from the database, fit the model, then write results back to Postgres for serving.

### **ARIMA and SARIMA with statsmodels**

ARIMA (AutoRegressive Integrated Moving Average) models the relationship between an observation and its lagged values, applies differencing to achieve stationarity, and models residual error as a moving average. SARIMA adds seasonal terms. The model requires iterative parameter estimation (selecting p, d, q values) that is not tractable in SQL.

A minimal implementation pulls pre-aggregated hourly data from PostgreSQL and fits an ARIMA model:

`import psycopg2
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA

conn = psycopg2.connect("host=localhost dbname=mydb user=postgres")
df = pd.read_sql(
    "SELECT bucket, avg_value FROM hourly_sensor_averages "
    "WHERE bucket > NOW() - INTERVAL '90 days' "
    "ORDER BY bucket",
    conn
)

model = ARIMA(df['avg_value'], order=(2, 1, 2))
result = model.fit()
forecast = result.forecast(steps=24)`

The query pulls from the continuous aggregate view, not the raw hypertable, so the data pull stays fast no matter how much raw data has accumulated.

ARIMA tends to outperform simpler methods when autocorrelation structure is strong and the series is stationary, or can be made stationary through differencing (subtracting each value from the one before it, to remove trend). For series with a lot of external factors or non-linear patterns, tree-based ML often beats ARIMA on structured business data with less tuning overhead.

ARIMA assumes the statistical properties of the series (mean, variance, autocorrelation) do not change over time. Non-stationary series with trends or changing variance violate this assumption and produce unreliable forecasts. The [<u>Augmented Dickey-Fuller (ADF) test</u>](https://www.statsmodels.org/stable/generated/statsmodels.tsa.stattools.adfuller.html) in Python's statsmodels.tsa.stattools is the standard check. The "I" component in ARIMA applies differencing to remove trends and achieve stationarity before fitting.

For deeper coverage of AR modeling concepts, see [<u>autoregressive time-series modeling</u>](https://www.tigerdata.com/learn/understanding-autoregressive-time-series-modeling).

### **Prophet for trend changepoints and holiday effects**

Prophet (Meta's open source library) is built for business time-series data with trend changepoints, seasonality, and known holiday or event effects. It handles missing data and outliers more gracefully than ARIMA and needs less manual parameter selection. The trade-off is that it needs more history for reliable changepoint detection, typically at least a year.

A minimal Prophet implementation with a PostgreSQL data pull:

`import psycopg2
import pandas as pd
from prophet import Prophet

conn = psycopg2.connect("host=localhost dbname=mydb user=postgres")
df = pd.read_sql(
    "SELECT bucket AS ds, avg_value AS y "
    "FROM hourly_sensor_averages "
    "WHERE bucket > NOW() - INTERVAL '365 days' "
    "ORDER BY bucket",
    conn
)

model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
model.fit(df)
future = model.make_future_dataframe(periods=24, freq='h')
forecast = model.predict(future)`

Prophet makes sense for demand series, web traffic, sales, and other business metrics with clear weekly and seasonal patterns and identifiable event effects. For univariate series without human-driven seasonality, ARIMA or a moving average is often more appropriate, and faster to run.

### **Multi-variate and multi-series forecasting at scale**

For IoT fleets with thousands of sensor series, or any use case that needs parallelized forecasting across many series, the Python ecosystem has purpose-built libraries for the job. Nixtla's StatsForecast and NeuralForecast, Darts, and sktime all treat forecasting as a batch-parallelizable operation. They handle cross-learning across series, multiple input features, and batch inference at a scale single-series ARIMA can't match.

The architectural pattern stays the same: Tiger Data handles ingestion and querying, Python handles forecasting, and results get written back to a forecasts table in PostgreSQL for downstream serving and alerting. What changes is the scale of the batch export and the parallelism in the Python layer.

For the Python-specific tutorial covering data pre-processing and model assessment, see [<u>time-series analysis and forecasting with Python</u>](https://www.tigerdata.com/learn/time-series-analysis-and-forecasting-with-python).

## **Foundation models as an emerging option**

Time-series foundation models are large models pre-trained on diverse time-series datasets, ranging from hundreds of millions to billions of time points. They can forecast new series zero-shot, without per-series training. The comparison to LLMs for text is reasonable given that they train on numerical sequences and generalize to unseen domains. This is emerging technology, not yet production-standard for most teams, but worth understanding for where the field is moving.

Three models have established themselves as benchmarks in this space:

- [**<u>Chronos-2</u>**](https://huggingface.co/amazon/chronos-2) **(Amazon Science):** Pre-trained on a large corpus of diverse time-series data producing probabilistic (quantile) forecasts. Extends beyond univariate to multivariate and covariate-informed forecasting in a single model, zero-shot via in-context learning. Available via HuggingFace. Practical for teams with short history per series or many distinct series to forecast.
- [**<u>TimesFM</u>**](https://huggingface.co/google/timesfm-2.5-200m-pytorch) **(Google Research):** The original TimesFM was trained on roughly 100 billion time points from Google-internal and public sources. The current release, TimesFM 2.5 (200M parameters, 16k context), shows strong long-horizon performance and is available as an open-weights model.
- [**<u>MOIRAI-2</u>**](https://huggingface.co/Salesforce/moirai-2.0-R-small) **(Salesforce):** Universal time-series forecasting model with strong zero-shot performance across diverse domains including energy, retail, and financial data. Available via HuggingFace.

For all three, the integration path is the same, where a Python script pulls data from PostgreSQL, runs inference using the model's API, and returns a forecast. TimescaleDB handles the data pipeline and the foundation model handles the compute.

Worth watching on the in-database side is [<u>tspDB (MIT)</u>](https://github.com/AbdullahO/tspdb), a PostgreSQL extension that builds a prediction index on top of a standard table. That index enables in-database predictive queries (forecasting, imputation, and confidence bounds) through `create_pindex()`. It's experimental and not production-ready as of mid-2026, but it points toward where the research is headed: natively integrated forecasting without a Python layer at all.

## **A simple forecasting pipeline in PostgreSQL**

The two-layer architecture below covers most SQL forecasting use cases.

Raw time-series data ingests into a hypertable. A continuous aggregate pre-computes hourly averages on a 5-minute refresh cycle. A retention policy drops raw data older than 90 days while keeping the aggregates intact. On top of that, a scheduled job (cron, Airflow, or another workflow orchestrator) pulls the pre-aggregated data via SQLAlchemy, fits ARIMA or Prophet, then writes forecast rows back to a forecasts table.

`CREATE TABLE forecasts (
  series_id        TEXT        NOT NULL,
  forecast_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  horizon_hours    INTEGER     NOT NULL,
  forecast_value   DOUBLE PRECISION NOT NULL,
  confidence_lower DOUBLE PRECISION,
  confidence_upper DOUBLE PRECISION,
  model_name       TEXT,
  PRIMARY KEY (series_id, forecast_at, horizon_hours)
);`

Writing forecasts back to Postgres gives you three concrete advantages. Downstream dashboards (Grafana, Metabase) query the forecasts table with standard SQL, no specialized tooling required. Alerting queries can join actual vs. forecast to detect deviation from expected trajectory. The forecast audit trail stays in the same system as the source data, which simplifies debugging and model versioning.

For context on retention policies and tiered data resolution that feed the pre-aggregation layer, see [<u>time-series downsampling</u>](https://www.tigerdata.com/learn/time-series-downsampling). For how to combine forecasting signals with anomaly alerts, see [<u>time-series anomaly detection</u>](https://www.tigerdata.com/learn/time-series-anomaly-detection-methods-sql-real-time-implementation).

## **A decision framework for method choice**

**Choose SQL-native moving averages if:**

- You need trend smoothing or rolling baselines for dashboards or alerting
- Your forecast horizon is short (hours to a few days)
- Your data is regular, or you can tolerate a simple row-based average
- Speed and simplicity matter more than model precision

**Choose SQL linear regression if:**

- The series has a near-linear trend over the recent window
- You're extrapolating 24 to 72 hours ahead
- You want a one-query implementation with no external dependencies

**Choose SQL exponential smoothing if:**

- Recent observations should outweigh older ones
- There is no seasonal component, or the seasonal period is handled elsewhere
- Dataset is small enough that recursive CTE performance is acceptable

**Choose Python (ARIMA / SARIMA) if:**

- Autocorrelation structure is strong and you need proper parameter selection
- Seasonality is a factor and a Holt-Winters SQL approximation is not accurate enough
- You need confidence intervals from a statistically rigorous model

**Choose Python (Prophet) if:**

- Your data has human-driven seasonality (weekly, holiday, business cycle patterns)
- Trend changepoints matter (sales, traffic, demand series with promotion events)
- You have enough history for changepoint detection (typically 1+ year)

**Choose Python with foundation models if:**

- You have many short series without enough per-series history for ARIMA or Prophet
- You want zero-shot forecasting without per-series training
- You're evaluating state-of-the-art options and can tolerate experimental tooling

In production, hybrid combinations are common, with SQL moving averages running real-time dashboards alongside ARIMA or Prophet in a nightly batch for medium-horizon forecasts, and both layers complementing each other without conflict.

The simplified diagram below walks through the same decision framework visually, from forecasting needs down to the specific method, with common hybrid pairings noted at the bottom.



## **Start building**

TimescaleDB runs on PostgreSQL. If you're already on Postgres and want to add time-series primitives (hypertables, continuous aggregates, time_weight()) to your forecasting stack, [<u>start a Tiger Cloud trial</u>](https://www.tigerdata.com/cloud/) or [<u>explore the TimescaleDB docs</u>](https://www.tigerdata.com/docs/) to get started with self-hosted deployment.

## **FAQ**

**What is time-series forecasting?**

Time-series forecasting uses historical time-indexed observations to estimate future values of a variable. It's predictive and forward-looking, which sets it apart from time-series analysis, which focuses on understanding patterns in past data. The main method families include statistical models (ARIMA, exponential smoothing), machine learning (tree-based models, LSTM), and emerging foundation models such as Chronos-2 and TimesFM.

**What are the main types of time-series forecasting models?**

The four main categories are naive and statistical methods (moving average, exponential smoothing), classical statistical models (ARIMA, SARIMA, Holt-Winters), machine learning models (XGBoost on time-indexed features, LSTM), and foundation models (Chronos-2, TimesFM, MOIRAI-2). Simpler models often match or outperform complex ones on short-horizon, well-behaved series.

**Can you do time-series forecasting in PostgreSQL without Python?**

Yes. Moving averages, time-weighted averages, and linear regression are all implementable in standard SQL using window functions and aggregate functions such as regr_slope() and regr_intercept(). TimescaleDB extends this with time_bucket() for consistent bucketing and time_weight() for irregular timestamps. For models that require iterative fitting (ARIMA, Prophet, ML models), Python is the appropriate layer on top of PostgreSQL.

**What is the difference between ARIMA and exponential smoothing?**

Exponential smoothing assigns decreasing weights to older observations and is computationally simple, making it a good baseline for trend-following series. ARIMA explicitly models autocorrelation structure and applies differencing to achieve stationarity. For short-horizon, trend-following series, exponential smoothing often performs comparably to ARIMA with less complexity. ARIMA tends to offer an advantage when autocorrelation patterns are strong and irregular.

**Why is stationarity important in time-series forecasting?**

ARIMA and related statistical models assume that the statistical properties of the series (mean, variance, and autocorrelation) do not change over time. Non-stationary series with trends or changing variance violate this assumption and produce unreliable forecasts. Differencing, the 'I' component in ARIMA, is the standard technique for removing trends and achieving stationarity before fitting the model.

**Is Holt-Winters better than ARIMA for seasonal data?**

There's no categorical answer here. Holt-Winters is simpler and interpretable, making it a strong baseline for series with stable seasonal patterns. SARIMA can model more complex seasonal structures and handles non-stationarity more explicitly. Both should be benchmarked against your data. For many short-horizon business forecasting problems, Holt-Winters performs comparably to SARIMA with less tuning overhead.

**When should I use Prophet instead of ARIMA?**

Meta’s Prophet is designed for business time-series with human-driven seasonality (weekly patterns, holiday effects, promotional events) and trend changepoints. It handles missing data and outliers well. ARIMA suits stationary series with strong autocorrelation structure where a formal statistical model with confidence intervals is required. Prophet generally needs at least one year of history for reliable changepoint detection.

**How do I use moving averages for time-series forecasting in SQL?**

Use a window function with AVG() over a ROWS BETWEEN N PRECEDING AND CURRENT ROW clause, combined with time_bucket() in TimescaleDB to group data into consistent time intervals. The result is a smoothed estimate of the current trend, useful for dashboards, anomaly detection baselines, and short-horizon projection when the trend is stable. For irregular timestamps, use time_weight() in place of a simple row-based window.

**What are foundation models for time-series forecasting?**

Foundation models such as Chronos-2, TimesFM, and MOIRAI-2 are large models pre-trained on diverse time-series datasets that can forecast new series zero-shot without per-series training. They use a Python API and connect to PostgreSQL data via standard database connectors. They're an emerging option showing strong benchmark performance, but are not yet production-standard for most teams.

**How do real-world events affect time-series forecasting accuracy?**

External events (promotions, outages, weather, economic shocks) create structural breaks that purely statistical models miss. Prophet handles known events via explicit holiday and event regressors. ARIMA requires manual intervention analysis for structural breaks. The practical recommendation is to flag known events in your data and evaluate model performance separately for event and non-event periods before deploying a forecast.

**What is the difference between time-series analysis and time-series forecasting?**

Time-series analysis is *retrospective*, identifying patterns, trends, seasonality, and anomalies in historical data. Time-series forecasting is *prospective*, using those patterns to estimate future values. Analysis informs forecasting, so you identify the trend and seasonal structure through analysis before selecting a forecasting model that can replicate that structure.

**How do I build a simple forecasting pipeline with PostgreSQL?**

The standard pattern starts with ingesting raw time-series data into a hypertable, then using a continuous aggregate to pre-compute hourly or daily averages on a refresh schedule. From there, a scheduled Python job pulls the pre-aggregated data, fits a forecasting model, and writes results back to a forecasts table in PostgreSQL. Downstream dashboards and alerting queries then join the forecasts table against actuals using standard SQL.