Prepared statements
Prepared statements are the final step for taking features to production. They pre-compile a feature and its entire dependency graph — including all data source calls and cross-source joins — into a single optimized query. This eliminates parsing and resolution overhead at request time, giving you predictable latency for real-time serving.
Defining the base features
A prepared statement wraps an existing feature. Start by creating the features you want to serve:
CREATE OR REPLACE FEATURES AS
SELECT
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A := INPUT(BIGINT),
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B := INPUT(BIGINT),
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C := INPUT(BIGINT),
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_D := FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A + FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B,
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_E := FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B + FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C,
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R := FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_D + FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_E| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_D | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_E | CREATED | Feature created as not exists |
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R | CREATED | Feature created as not exists |
Here, FEATURE_R depends on FEATURE_D and FEATURE_E, which in turn depend on the three inputs FEATURE_A, FEATURE_B, and FEATURE_C. The prepared statement will resolve this entire dependency chain at compile time.
In a real deployment, these features would typically include EXTERNAL_REDIS(), EXTERNAL_COLUMNS(), or EXTERNAL_HTTP() calls — the prepared statement compiles all of that into a single execution plan.
Creating the prepared statement
Use PREPARED_STATEMENT() to compile a feature for real-time serving:
CREATE OR REPLACE FEATURE FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS AS
PREPARED_STATEMENT(
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R
USING INPUTS
(FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B),
FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C
);| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS | CREATED | Feature created as not exists |
The USING INPUTS clause declares how inputs are grouped. Inputs in the same parenthesized group (FEATURE_A, FEATURE_B) are paired row-by-row (like BIND_VALUES with an array of rows). Inputs in separate groups create a cross-product. This grouping controls how the prepared statement handles multiple input rows.
You can inspect the compiled result with DESCRIBE:
DESCRIBE FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS| OUTPUT VARCHAR |
|---|
| [{name: FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS, function: PREPARED_STATEMENT, params: {base: {kind: IDENTIFIER}, inputs: {kind: ARRAY}, positions: {kind: ARRAY}}, status: DEV, position: 0.0, meta: {}, properties: {}, unnamed_dependencies: {}, id: 2T9UB2NUA23E, feature_type: {}, input: [], signature: NULL, dependencies: {}, restrictions: [], inputs: [], formula: NULL, computed_properties: {prepared_statement_details: {query_sql: WITH\nCTE_2Z25ZAE6_0 AS (\n SELECT\n source_UNNAMED_HVTDUSTQ.\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\" AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \n source_UNNAMED_HVTDUSTQ.\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\", \n source_UNNAMED_8YY5DTNS.\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\" AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\"\n FROM (\n \tSELECT 1\n ) source_CONSTANTS\n CROSS JOIN (\n \tWITH\n\tCTE_JRVYMYHN_0 AS (\n\t(\n\t SELECT t.*\n\t FROM (SELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" FROM INPUT_TABLE_1)\n\t AS t(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\")\n\t )\n\t), \n\tCTE_JRVYMYHN_1 AS (\n\tSELECT\n\t\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \n\t\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\"\n\tFROM CTE_JRVYMYHN_0\n\t)\n\tSELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\"\n\tFROM CTE_JRVYMYHN_1\n ) source_UNNAMED_HVTDUSTQ\n CROSS JOIN (\n \tWITH\n\tCTE_2QMB49D2_0 AS (\n\t(\n\t SELECT t.*\n\t FROM (SELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\" FROM INPUT_TABLE_2)\n\t AS t(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\")\n\t )\n\t), \n\tCTE_2QMB49D2_1 AS (\n\tSELECT\n\t\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\"\n\tFROM CTE_2QMB49D2_0\n\t)\n\tSELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\"\n\tFROM CTE_2QMB49D2_1\n ) source_UNNAMED_8YY5DTNS\n), \nCTE_2Z25ZAE6_1 AS (\nSELECT\n\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \n\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\", \n\tCAST(\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\" AS BIGINT) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\"\nFROM CTE_2Z25ZAE6_0\n), \nCTE_2Z25ZAE6_2 AS (\nSELECT\n\t((\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\" + \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\") + (\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" + \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\")) AS \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS\", \n\t\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \n\t\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\", \n\t\"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\"\nFROM CTE_2Z25ZAE6_1\n)\nSELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS\"\nFROM CTE_2Z25ZAE6_2\n, input_schema: {INPUT_TABLE_1: [[FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, BIGINT], [FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B, BIGINT]], INPUT_TABLE_2: [[FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C, BIGINT]]}, output_schema: [[FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, BIGINT], [FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B, BIGINT], [FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C, BIGINT], [FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS, BIGINT]], query_featureql: \n WITH\n \n \n (FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B) :=\n BIND_SQL(SELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A\", \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B\" FROM INPUT_TABLE_1)\n ,\n \n \n FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C :=\n BIND_SQL(SELECT \"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\" FROM INPUT_TABLE_2)\n ,\n \n PREPARED_STATEMENT_AS_MACRO := MACRO(\n FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R\n USING INPUTS FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C\n )\n SELECT\n \n FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A,\n \n FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B,\n \n FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C,\n \n PREPARED_STATEMENT_AS_MACRO(FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B, FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C) AS FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS\n }}}] |
Calling a prepared statement
From Python
import requests
def call_prepared_statement(feature_name: str, inputs: dict[str, list]) -> dict:
response = requests.post(
"http://localhost:10090/v1/featureql",
json={
"statement": {
"id": feature_name,
"inputs": inputs
}
},
headers={
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
},
timeout=30,
)
response.raise_for_status()
return response.json() From curl
curl -X POST "http://localhost:10090/v1/featureql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"statement": {
"id": "FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_R_PS",
"inputs": {
"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_A": [1, 2],
"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_B": [10, 20],
"FM.TUTORIALS.PREPARED_STATEMENTS.FEATURE_C": [100]
}
}
}' The inputs object maps each input feature name to an array of values. Inputs in the same group must have arrays of equal length (they are paired positionally). Inputs in different groups can have different lengths (cross-product).