Hybrid queries

Hybrid queries let you embed FeatureQL inside SQL — or SQL inside FeatureQL — so a SQL client, BI tool, or application can use FeatureQL features without changing its workflow.

This page is the live-compilation door. For a frozen SQL table, use persisted views . For the whole edge, see SQL interoperability .

Language directives

When FeatureMesh receives a query, it needs to know which language to parse. Use comment directives to be explicit:

/* FEATUREQL */  -- Parse as FeatureQL
/* SQL */        -- Parse as SQL
sql

SQL, FEATUREQL, and STRICT share the same comment forms: /* TAG */ anywhere, or -- TAG as the first line when that comment is exactly the tag (-- SQL: note is an ordinary comment). Prefer the /* … */ form — that is what docs and error messages show.

Without a directive, FeatureMesh auto-detects the language. Explicit directives are safer, especially in hybrid queries where both languages appear.

Two embedding syntaxes

FeatureQL blocks can be embedded in SQL using two syntaxes:

SyntaxExampleWhen to use
Parentheses (default)FEATUREQL(RETURN ...)Standard syntax — use this by default
CommentFEATUREQL/* RETURN ... */For SQL clients that parse or introspect queries and choke on the parentheses form

The parentheses syntax is cleaner and supports nesting. The comment syntax exists because some external tools (BI platforms, SQL linters, query formatters) inspect query text and may reject FEATUREQL(...) as invalid SQL. Wrapping the FeatureQL body in a comment (/* ... */) makes it invisible to those tools.

Both syntaxes produce identical results. Pick the one that works with your toolchain.

FeatureQL as a subtable

Wrap a FeatureQL query in FEATUREQL(...) to use it as a table expression inside SQL. The outer SQL can filter, join, or aggregate the results:

FeatureQL
SELECT
    STORE_IS_BCN,
    COUNT() AS NUM
FROM FEATUREQL(
    DEFINE
        STORES := ENTITY(),
        STORE_ID := INPUT(BIGINT#STORES),
        DIM_STORE_ATTRIBUTES := EXTERNAL_COLUMNS(
            store_id BIGINT#STORES BIND TO STORE_ID,
            store_city VARCHAR
            FROM SQL(SELECT store_id, store_city FROM (VALUES (1, 'BCN'), (2, 'BCN'), (3, 'MAD'), (4, 'MAD'), (5, 'VAL')) AS t(store_id, store_city))
        ),
        STORE_CITY := DIM_STORE_ATTRIBUTES[store_city],
    RETURN
        STORE_ID := BIND_VALUES(ARRAY[1,2,3,4,5]),
        STORE_CITY,
        STORE_CITY='BCN' AS STORE_IS_BCN
)
GROUP BY (STORE_IS_BCN)
Result
STORE_IS_BCN BOOLEANNUM BIGINT
false3
true2

The FeatureQL block computes features for each store, and the outer SQL groups the results by STORE_IS_BCN — a pattern that's natural when your BI tool expects SQL but your metrics live in FeatureQL.

Metric additivity, units, and multiplicity are checked inside the FEATUREQL(...) island. An outer SQL SUM, AVG, or GROUP BY is ordinary SQL and is not re-validated. Keep rollups that must preserve meaning inside FeatureQL; use the outer SQL layer for presentation, client limits, and joins that your tool already owns.

FeatureQL as a CTE

FeatureQL blocks work as Common Table Expressions too. This separates feature computation from downstream analysis:

FeatureQL
WITH
a AS FEATUREQL(
    DEFINE
        FEATURE1 := INPUT(BIGINT)
    RETURN
        FEATURE1 := BIND_VALUES(SEQUENCE(1,10)),
        FEATURE2 := FEATURE1 * 2
)
SELECT * FROM a WHERE FEATURE2 < 6
Result
FEATURE1 BIGINTFEATURE2 BIGINT
12
24

The CTE a computes features via FeatureQL, and the outer query filters with standard SQL. You can reference the same CTE multiple times in the main query, and the engine caches the results.

The same query using the comment syntax:

FeatureQL
WITH
a AS FEATUREQL/*
    DEFINE
        FEATURE1 := INPUT(BIGINT)
    RETURN
        FEATURE1 := BIND_VALUES(SEQUENCE(1,10)),
        FEATURE2 := FEATURE1 * 2
*/
SELECT * FROM a WHERE FEATURE2 < 6
Result
FEATURE1 BIGINTFEATURE2 BIGINT
12
24

Working with SQL clients

Many SQL clients (Tableau, DBeaver, Superset) automatically wrap user queries:

SELECT * FROM (/* User's query */) LIMIT 1000
sql

Hybrid syntax fits naturally into this pattern. The example below uses the comment syntax (FEATUREQL/* ... */), which is better suited here — SQL clients that introspect the outer query won't be confused by the embedded FeatureQL:

FeatureQL
SELECT *, FEATURE1 + FEATURE2 AS SQL_COLUMN
FROM (
    FEATUREQL /*
        DEFINE
            FEATURE1 := INPUT(BIGINT)
        RETURN
            FEATURE1 := BIND_VALUES(SEQUENCE(1,10)),
            FEATURE2 := FEATURE1 * 2
    */
)
ORDER BY FEATURE1
LIMIT 3
Result
FEATURE1 BIGINTFEATURE2 BIGINTSQL_COLUMN BIGINT
123
246
369

The SQL_COLUMN in the outer query demonstrates that you can reference FeatureQL output columns in SQL expressions — the boundary between the two languages is transparent.

Compatible clients: Tableau, PowerBI, Looker, Superset, DataGrip, DBeaver, and any application using JDBC/ODBC.

Limitations

  • FeatureQL does not optimize across language boundaries — each FeatureQL block is compiled independently, and filter pushdown depends on the underlying engine.
  • Semantic checks (additivity, units, multiplicity) apply inside each FEATUREQL(...) island. Outer SQL aggregates are ordinary SQL and are not re-validated. That is the language split, not a missing FeatureQL rule.
  • Hybrid queries are not supported on the DataFusion / serving backend. Same answer across backends lists that gap next to other engine-true limits.
  • When possible, prefer pure FeatureQL for better maintainability and optimization. Use hybrid queries when you need to integrate with existing SQL workflows or tools that require SQL input.

A persisted view is the other SQL door: compile once, scan stored SQL. Hybrid recompiles the FeatureQL island on every request.

Next

Continue with persisted views when a BI tool should discover an ordinary table, or with the JDBC catalog when that tool lists schemas before it runs SQL.