Mapping to data sources

Features become useful when they connect to real data. Data source mapping is how you bring external data — whether embedded in the query or stored in a database — into FeatureQL's feature graph.

Every column-based mapping follows the same pattern: declare columns, bind key columns to inputs, and specify a source via FROM. The operator and source type change, but the shape stays the same.

The general pattern

FEATURE_NAME := <OPERATOR>(
    key_column TYPE BIND TO SOME_INPUT,
    value_column TYPE
    FROM <SOURCE>(...)
)
sql

The BIND TO clause links a key column to an INPUT() feature, enforcing a cardinality constraint: the source must return 0 or 1 row per unique input combination. If your source can return multiple rows for a given key, results will be unpredictable.

You access columns of the result using the [] operator:

CUSTOMER_NAME := FEATURE_NAME[value_column]
sql

Column-based mappings

These operators return a ROW. You pick columns from that row with [].

OperatorSourceWhat it does
INLINE_COLUMNS()CSV(), JSON()Embeds data directly in the query — useful for prototyping and tests
EXTERNAL_COLUMNS()TABLE(), SQL()Maps to columns in external database tables or queries
EXTERNAL_COLUMNS()VIEW()Maps to views in external databases (realtime serving)

The only difference between INLINE_COLUMNS() and EXTERNAL_COLUMNS() is where the data lives. The syntax inside the parentheses is identical.

For a detailed comparison of EXTERNAL_COLUMNS() source types and when to use each, see External Columns .

Text-based mappings

These operators return a VARCHAR instead of a row. You parse the result yourself (to JSON or whatever format you need).

OperatorWhat it doesDetails
EXTERNAL_REDIS()Maps to a Redis keySee External Redis
EXTERNAL_HTTP()Maps to an HTTP endpointSee External HTTP

Inline data with CSV

The simplest way to get started: embed the data directly in your query. INLINE_COLUMNS() with CSV() lets you define a small dataset inline — no external tables needed.

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    DIM_CUSTOMERS := INLINE_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        name VARCHAR
        FROM CSV(
            customer_id,name
            101,Alice
            102,Bob
            103,Charly
            104,Daniel
            105,Elody
            106,Francis
        )
    )
SELECT
    CUSTOMER_ID,
    DIM_CUSTOMERS,
    CUSTOMER_NAME := DIM_CUSTOMERS[name]
FOR
    CUSTOMER_ID := BIND_VALUES(ARRAY[101, 102, 103, 104, 105, 106]);
Result
CUSTOMER_ID BIGINTDIM_CUSTOMERS ROWCUSTOMER_NAME VARCHAR
101{customer_id: 101, name: Alice}Alice
102{customer_id: 102, name: Bob}Bob
103{customer_id: 103, name: Charly}Charly
104{customer_id: 104, name: Daniel}Daniel
105{customer_id: 105, name: Elody}Elody
106{customer_id: 106, name: Francis}Francis

Inline data with JSON

The same approach works with JSON. This is handy when your data comes from an API response or you prefer JSON syntax over CSV.

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    DIM_CUSTOMERS := INLINE_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        name VARCHAR
        FROM JSON(
            [
                {"customer_id": 101, "name": "Alice"},
                {"customer_id": 102, "name": "Bob"},
                {"customer_id": 103, "name": "Charly"},
                {"customer_id": 104, "name": "Daniel"},
                {"customer_id": 105, "name": "Elody"},
                {"customer_id": 106, "name": "Francis"}
            ]
        )
    )
SELECT
    CUSTOMER_ID,
    DIM_CUSTOMERS,
    CUSTOMER_NAME := DIM_CUSTOMERS[name]
FOR
    CUSTOMER_ID := BIND_VALUES(ARRAY[101, 102, 103, 104, 105, 106]);
Result
CUSTOMER_ID BIGINTDIM_CUSTOMERS ROWCUSTOMER_NAME VARCHAR
101{customer_id: 101, name: Alice}Alice
102{customer_id: 102, name: Bob}Bob
103{customer_id: 103, name: Charly}Charly
104{customer_id: 104, name: Daniel}Daniel
105{customer_id: 105, name: Elody}Elody
106{customer_id: 106, name: Francis}Francis

External data with TABLE

When your data lives in a database table, use EXTERNAL_COLUMNS() with TABLE(). The example below assumes this table exists:

FeatureQL
/* SQL */
CREATE SCHEMA IF NOT exists ext;
--
CREATE OR REPLACE TABLE ext.dim_customers AS
SELECT customer_id, name FROM (
    VALUES
    (101, 'Alice'),
    (102, 'Bob'),
    (103, 'Charly'),
    (104, 'Daniel'),
    (105, 'Elody'),
    (106, 'Francis')
) as t(customer_id, name);
Result
Count BIGINT
6

With the table in place, the mapping looks almost identical to the inline version — only the source changes from CSV(...) to TABLE(schema.table):

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    DIM_CUSTOMERS := EXTERNAL_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        name VARCHAR
        FROM TABLE(ext.dim_customers)
    )
SELECT
    CUSTOMER_ID,
    DIM_CUSTOMERS,
    CUSTOMER_NAME := DIM_CUSTOMERS[name]
FOR
    CUSTOMER_ID := BIND_VALUES(ARRAY[101, 102, 103, 104, 105, 106]);
Result
CUSTOMER_ID BIGINTDIM_CUSTOMERS ROWCUSTOMER_NAME VARCHAR
101{customer_id: 101, name: Alice}Alice
102{customer_id: 102, name: Bob}Bob
103{customer_id: 103, name: Charly}Charly
104{customer_id: 104, name: Daniel}Daniel
105{customer_id: 105, name: Elody}Elody
106{customer_id: 106, name: Francis}Francis

External data with SQL

When you need to transform or filter data before mapping, use SQL() instead of TABLE(). This lets you write a SQL subquery as the source:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    DIM_CUSTOMERS := EXTERNAL_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        name VARCHAR
        FROM SQL(SELECT customer_id, name FROM ext.dim_customers)
    )
SELECT
    CUSTOMER_ID,
    DIM_CUSTOMERS,
    CUSTOMER_NAME := DIM_CUSTOMERS[name]
FOR
    CUSTOMER_ID := BIND_VALUES(ARRAY[101, 102, 103, 104, 105, 106]);
Result
CUSTOMER_ID BIGINTDIM_CUSTOMERS ROWCUSTOMER_NAME VARCHAR
101{customer_id: 101, name: Alice}Alice
102{customer_id: 102, name: Bob}Bob
103{customer_id: 103, name: Charly}Charly
104{customer_id: 104, name: Daniel}Daniel
105{customer_id: 105, name: Elody}Elody
106{customer_id: 106, name: Francis}Francis

External data with VIEW (realtime serving)

Views work the same way as TABLE() but are used in realtime serving contexts, where the data source is a live database view rather than a batch table:

WITH
    CUSTOMER_ID := INPUT(BIGINT),
    DIM_CUSTOMERS := EXTERNAL_COLUMNS(
        customer_id BIGINT BIND TO CUSTOMER_ID,
        name VARCHAR
        FROM VIEW(ext.v_dim_customers)
    )
SELECT
    CUSTOMER_ID,
    DIM_CUSTOMERS,
    CUSTOMER_NAME := DIM_CUSTOMERS[name]
FOR
    CUSTOMER_ID := BIND_VALUES(ARRAY[101, 102, 103, 104, 105, 106]);
sql

For more on realtime connectors (Redis, JDBC, HTTP), see Realtime serving context .

Last update at: 2026/03/18 16:18:57
Last updated: 2026-03-18 16:19:34