> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/sdgm/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/sdgm/_mcp/server.

# Tables

> Select source columns and define the keys, timestamps, and feature types used by your graph

Create a `Table` to describe the source columns Kumo should use and their roles in a graph.
The table API is the same on Linux and in Snowflake or Databricks notebooks. Complete the setup for your environment and connect your data using [Linux](/kumo-engine/1.0.0/user-code-guide/connect-your-data-linux), [Snowflake](/kumo-engine/1.0.0/user-code-guide/connect-your-data-snowflake), or [Databricks](/kumo-engine/1.0.0/user-code-guide/connect-your-data-databricks) before continuing.
A source table identifies external data. A Kumo `Table` adds modeling metadata; it does not copy the complete source into a graph until you create a snapshot.

## Create Tables from a Source

The following examples use the AdventureWorks customer, product, and order sources.

```python
from kumo.engine import Table

customer = Table.from_source_table(
    connector["Customer"],
    ["CustomerID", "StoreID", "TerritoryID"],
    primary_key="CustomerID",
)
product = Table.from_source_table(
    connector["Product"],
    ["ProductID", "Name", "Color", "ProductLine", "Class", "StandardCost", "ListPrice"],
    primary_key="ProductID",
)
sales_order_header = Table.from_source_table(
    connector["SalesOrderHeader"],
    ["SalesOrderID", "CustomerID", "OrderDate", "OnlineOrderFlag", "TerritoryID", "SubTotal", "TotalDue"],
    primary_key="SalesOrderID",
    time_column="OrderDate",
)
```

`from_source_table()` resolves column types and may sample the source when its schema is ambiguous.
Specify key and time columns explicitly; Kumo does not infer their roles from their names.
A primary key identifies an entity. A time column tells Kumo when an event or record occurred.

```text
Table.from_source_table(
    source_table,
    column_names=None,
    *,
    primary_key=None,
    time_column=None,
    end_time_column=None,
) -> Table
```

| Argument          | Purpose                                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `column_names`    | Select source columns. Omit it to include all columns. Include every key, time column, target, and feature your workflow needs. |
| `primary_key`     | Name the column identifying each entity in this table.                                                                          |
| `time_column`     | Name the timestamp used for temporal modeling.                                                                                  |
| `end_time_column` | Name the end timestamp for records with a validity interval.                                                                    |

## Create Tables from SQL

Filter rows, join source tables, or derive columns in your warehouse before adding them to a graph.
Use a configured Snowflake, Databricks, or BigQuery connector with its matching Kumo integration and permission to query every referenced table.
File and object-storage connectors do not support custom SQL.

The example below uses the `bigquery` connector from [Connect Your Data](/kumo-engine/1.0.0/user-code-guide/connect-your-data-linux#bigquery), with an AdventureWorks `Customer` table in its configured dataset.
It keeps customers with a territory and projects the columns the graph needs.
Use the identifier spelling and quoting required by your warehouse.

```python
from kumo.engine import Graph, Table

customer_sql = """
SELECT CustomerID, StoreID, TerritoryID
FROM Customer
WHERE TerritoryID IS NOT NULL
"""
customer_from_sql = Table.from_sql(
    bigquery,
    customer_sql,
    primary_key="CustomerID",
)
print([column.name for column in customer_from_sql.definition.cols])
```

The resolved columns should match the query's output: `CustomerID`, `StoreID`, and `TerritoryID`, with the warehouse's identifier casing.
Use those returned names for `primary_key`, `time_column`, `end_time_column`, and any optional `column_names` selection.
Include every key, timestamp, target, and feature needed by your workflow in the projection.
If a key is renamed with `AS`, use its new name in the table definition.

The equivalent source-object form is useful when you want to keep the SQL source for reuse:

```python
customer_source = bigquery.sql(customer_sql)
customer_from_sql = Table.from_source_table(
    customer_source,
    primary_key="CustomerID",
)
```

Pass this source object to `from_source_table()`, not a raw SQL string.
Each construction resolves metadata from the query result and can execute warehouse SQL.
Choose one form rather than running both for the same table.

| Connector  | Namespace for query references           |
| ---------- | ---------------------------------------- |
| Snowflake  | Configured `database` and `schema_name`  |
| Databricks | Configured `catalog` and `schema_name`   |
| BigQuery   | Configured `project_id` and `dataset_id` |

Unqualified table names resolve inside that namespace.
Qualified names must stay inside it, including references in joins and common table expressions (CTEs).
Use a single read-only `SELECT`, optionally preceded by CTEs; writes, schema changes, and multiple statements are rejected.
Supply a fully bound query without unresolved parameter placeholders.
Return flat, named output columns and give every computed expression an explicit alias, for example `TotalDue - SubTotal AS additional_charges`.
If Kumo cannot verify the query's dependencies, simplify it so its source references can be resolved within the configured namespace.

SQL sources receive generated source names.
Give the table a readable graph alias so predictive queries and relationships have a stable name:

```python
customer_graph = Graph(tables={"customers": customer_from_sql}, edges=[])
customer_graph.validate()
customer_snapshot = customer_graph.snapshot()
```

This creates a graph snapshot containing the filtered customer rows.
Use `customers` as the table name in predictive queries and graph edges.
When adding relationships, their foreign keys must name columns projected by the SQL query; see [Create a Graph](/kumo-engine/1.0.0/user-code-guide/set-up-a-graph/graphs#create-a-graph).
Snapshot creation runs the query again, so changes to source data since metadata inference can change the rows ingested.
Keep the output schema stable between these steps, ensure declared primary keys remain unique after joins, and check that the query returns rows: an empty result cannot be ingested.
Local snapshot creation still requires the configured execution storage from your environment setup.

## Inspect and Adjust Column Types

Inspect the resolved definition before building the graph.
Check that all requested keys, time columns, targets, and features appear in the resolved column list:

```python
for column in customer.definition.cols:
    print(column.name, column.dtype, column.stype)
print("Primary key:", customer.definition.pkey)
print("Time column:", customer.definition.time_col)
```

A column's `dtype` describes its representation, such as an integer or timestamp.
Its `stype` describes how the model should interpret it, such as an identifier, category, or numerical feature.

For a tabular view, project the resolved column names, types, and roles into a pandas DataFrame.
This example uses the `customer` table created above:

```python
import pandas as pd

definition = customer.definition
metadata = pd.DataFrame([
    {
        "name": column.name,
        "dtype": column.dtype.value,
        "stype": column.stype.value,
        "primary_key": column.name == definition.pkey,
        "time_column": column.name == definition.time_col,
        "end_time_column": column.name == definition.end_time_col,
    }
    for column in definition.cols
])
print(metadata.to_string(index=False))
```

The result has one row per selected column.
For this customer table, `CustomerID` has `primary_key=True`; both time-role columns are `False` for every row because no time columns were assigned.
The projection reads the resolved metadata in memory and includes no source row values or connector settings.
If you constructed a bare `Table` with `definition=None`, first resolve it with `table = table.infer_metadata()` as described in [Refresh Metadata](#refresh-metadata).
Inference can read the source; displaying an already resolved definition does not refresh it.
See [Inspect a Graph Definition](/kumo-engine/1.0.0/user-code-guide/set-up-a-graph/graphs#inspect-a-graph-definition) to list graph table names and relationships.

See [Encoder Selection](/kumo-engine/1.0.0/user-code-guide/set-up-a-graph/encoder-selection#supported-encoders) for compatible encoders and configuration options for each semantic type.
Use `with_column()` to return a new table with an explicit override:

```python
from kumoapi.typing import Dtype, Stype

sales_order_header = sales_order_header.with_column("CustomerID", stype=Stype.ID)
sales_order_header = sales_order_header.with_column("OrderDate", dtype=Dtype.date)
```

For a CSV timestamp stored as text, supply its format. This example expects values such as `2026-01-15 14:30:00`:

```python
sales_order_header = sales_order_header.with_column(
    "OrderDate",
    dtype=Dtype.date,
    timestamp_format="%Y-%m-%d %H:%M:%S",
)
```

```text
table.with_column(
    name,
    *,
    stype=None,
    dtype=None,
    timestamp_format=None,
    multicategorical_delimiter=None,
) -> Table
```

Assign the returned value; the original table is unchanged.
Unspecified semantic type, data type, and timestamp format preserve their current values. An unknown column raises `ValueError`.
For a scalar string multicategorical column, an omitted delimiter preserves an existing delimiter or samples the source to infer a missing one. If the resulting column is not scalar string multicategorical, an omitted delimiter clears the previous delimiter.
For Unix timestamps, `timestamp_format` can instead use a `TimestampUnit` from `kumoapi.table`.
For a delimited collection of categories, use the appropriate `MulticategoricalDelimiter` from `kumoapi.typing`.

## Validate a Table Schema

Check a table's resolved schema before adding relationships or creating a snapshot.
Use the `customer` table created above, including any type overrides you want to check:

```python
from kumo.engine import Graph

Graph(tables=[customer]).validate()
```

The call returns `None` when the schema is valid. If it raises a validation error, correct the named column or type and validate the revised table again.
For example, a floating-point primary key is rejected. Use an integer or string key in your source and ensure the table's declared type matches it.

This checks the resolved schema. It does not ingest the table or verify row constraints such as primary-key uniqueness.
Creating the table with `from_source_table()` still reads source metadata and may sample data to resolve types.
To check relationships between tables, [create a graph with edges and validate it](/kumo-engine/1.0.0/user-code-guide/set-up-a-graph/graphs#create-a-graph). That section also explains the limitation on self-relations.

## Refresh Metadata

`table.infer_metadata()` returns a new table inferred from the current source, preserving the selected columns and named keys and time columns.
It is not necessary immediately after `from_source_table()`.
If you rerun inference, reapply any explicit type overrides before building the graph.
To change selected columns or key roles, construct a new table with the desired arguments.

Continue to [Graphs](/kumo-engine/1.0.0/user-code-guide/set-up-a-graph/graphs) to connect these tables and materialize a snapshot.