# Help: `chained_transform` (concept)

To aggregate, then re-aggregate (or filter after grouping), chain two `.TRANSFORM()` calls. Each one operates on the output of the previous.

## Aggregate, then take the top result

```featureql
-- Step 1: group and sum within the array
REVENUE_BY_CATEGORY := ENRICHED_ORDERS.TRANSFORM(
    RETURN category, SUM(amount) GROUP BY category AS total
    ORDER BY total DESC
),
-- Step 2: pick the single top row from the aggregated result
TOP_CATEGORY := REVENUE_BY_CATEGORY.TRANSFORM(
    RETURN category, total ORDER BY total DESC LIMIT 1
),
```

Runnable snippet: [`help_chained_transform_top`](/llms/help_chained_transform_top.md)

## Filter after a GROUP BY (HAVING equivalent)

```featureql
-- WHERE in the second TRANSFORM acts on the grouped result
ACTIVE_CATEGORIES := REVENUE_BY_CATEGORY.TRANSFORM(
    RETURN * WHERE total > 100
),
```

Runnable snippet: [`help_chained_transform_filter`](/llms/help_chained_transform_filter.md)

## Deduplicate after enrichment

```featureql
-- First enrichment, then keep one row per key
ENRICHED := EXTEND(ZIP(order_ids AS order_id) WITH ORDER_DATE, ORDER_STATUS VIA order_id BIND TO ORDER_ID),
LATEST    := ENRICHED.TRANSFORM(
    RETURN *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY ORDER_DATE DESC) AS rn
).TRANSFORM(
    RETURN * WHERE rn = 1
),
```

## ⚠ You cannot GROUP BY twice in a single TRANSFORM

Each TRANSFORM is one pass over its input. Nesting GROUP BY inside a single TRANSFORM is not valid — split it into two.

Nested `.TRANSFORM()` inside an inner RETURN is also forbidden — use successive chains on the array; see [`transform_forbidden`](/llms/transform_forbidden.md) and the examples above.

# Help: `chained_transform` (snippet)

## `help_chained_transform_top`

```slt
# tags: chained_transform, nested_aggregation, successive_aggregation, transform_limit
# schema: ARRAY<ROW<category VARCHAR, total BIGINT>>
query TOP:Q
DEFINE
    orders := ZIP(
        ARRAY('A', 'B', 'A', 'C') AS category,
        ARRAY(100::BIGINT, 200::BIGINT, 50::BIGINT, 300::BIGINT) AS amount
    ),
    revenue_by_category := orders.TRANSFORM(
        RETURN
            category,
            total := SUM(amount) GROUP BY (category)
        ORDER BY total DESC
    ),
    top_category := revenue_by_category.TRANSFORM(
        RETURN
            category,
            total
        ORDER BY total DESC
        LIMIT 1
    )
RETURN
    top := top_category
;
----
[{category: C, total: 300}]
```

## `help_chained_transform_filter`

```slt
# tags: chained_transform, transform_group_by, where_vs_filter
# schema: ARRAY<ROW<category VARCHAR, total BIGINT>>
query ACTIVE:Q
DEFINE
    orders := ZIP(
        ARRAY('A', 'B', 'A') AS category,
        ARRAY(100::BIGINT, 200::BIGINT, 50::BIGINT) AS amount
    ),
    revenue_by_category := orders.TRANSFORM(
        RETURN
            category,
            total := SUM(amount) GROUP BY (category)
        ORDER BY total DESC
    ),
    active_categories := revenue_by_category.TRANSFORM(
        RETURN *
        WHERE total > 100
    )
RETURN
    active := active_categories
;
----
[{category: B, total: 200}, {category: A, total: 150}]
```

**Related tags**: [`transform_forbidden`](/llms/transform_forbidden.md) [`transform_aggregate`](/llms/where_vs_filter.md) [`transform_group_by`](/llms/transform_window.md) [`deduplication`](/llms/deduplication.md) [`top_n`](/llms/deduplication.md)
