Skip to content

Register and join tables

Register multiple tables in one Session to join them with DataFusion SQL. Complete the first-table tutorial before starting.

Run the example

Run this example from an empty working directory. It creates prices and volumes tables, registers both, and joins them on (ts, symbol).

from __future__ import annotations

from pathlib import Path

import pyarrow as pa
import pyarrow.parquet as pq

import timeseries_table_format as ttf


def _write_prices_parquet(path: Path) -> None:
    tbl = pa.table(
        {
            "ts": pa.array([0, 3_600 * 1_000_000], type=pa.timestamp("us")),
            "symbol": pa.array(["NVDA", "NVDA"], type=pa.string()),
            "close": pa.array([1.0, 2.0], type=pa.float64()),
        }
    )
    pq.write_table(tbl, str(path))


def _write_volumes_parquet(path: Path) -> None:
    tbl = pa.table(
        {
            "ts": pa.array([0, 3_600 * 1_000_000], type=pa.timestamp("us")),
            "symbol": pa.array(["NVDA", "NVDA"], type=pa.string()),
            "volume": pa.array([10, 20], type=pa.int64()),
        }
    )
    pq.write_table(tbl, str(path))


def _parquet_reader(path: Path) -> pa.RecordBatchReader:
    parquet_file = pq.ParquetFile(path)
    return pa.RecordBatchReader.from_batches(
        parquet_file.schema_arrow,
        parquet_file.iter_batches(),
    )


def run(*, base_dir: Path) -> pa.Table:
    prices_root = base_dir / "prices_tbl"
    prices = ttf.TimeSeriesTable.create(
        table_root=str(prices_root),
        index_column="ts",
        index_type="timestamp",
        index_granularity="1h",
        entity_columns=["symbol"],
        timezone=None,
    )
    prices_seg = base_dir / "prices.parquet"
    _write_prices_parquet(prices_seg)
    prices.append(_parquet_reader(prices_seg))

    volumes_root = base_dir / "volumes_tbl"
    volumes = ttf.TimeSeriesTable.create(
        table_root=str(volumes_root),
        index_column="ts",
        index_type="timestamp",
        index_granularity="1h",
        entity_columns=["symbol"],
        timezone=None,
    )
    volumes_seg = base_dir / "volumes.parquet"
    _write_volumes_parquet(volumes_seg)
    volumes.append(_parquet_reader(volumes_seg))

    sess = ttf.Session()
    sess.register_tstable("prices", str(prices_root))
    sess.register_tstable("volumes", str(volumes_root))

    return sess.sql(
        """
        select p.ts as ts, p.symbol as symbol, p.close as close, v.volume as volume
        from prices p
        join volumes v
        on p.ts = v.ts and p.symbol = v.symbol
        order by p.ts
        """
    )


def main() -> None:
    out = run(base_dir=Path("./my_tables"))
    print(out)


if __name__ == "__main__":
    main()

Use stable SQL names when registering tables so application queries do not depend on filesystem paths.

You can also mix managed tables and plain Parquet data in one session:

  • register_tstable(...) registers a managed table root.
  • register_parquet(...) registers an unmanaged Parquet file or directory.

See the Session reference for the complete API.