> ## Documentation Index
> Fetch the complete documentation index at: https://docs.helix-db.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Projections

> For the complete documentation index optimized for AI agents, see [llms.txt](/llms.txt).

Up to this point every example has ended with `.valueMap([...])` to ship a few
properties back to the caller. This page covers the full set of terminal projections:
how to shape the output, compute derived values, and aggregate the stream.

A terminal step (`.count`, `.values`, `.project`, ...) ends the chain and produces a
result rather than another stream. TypeScript and Rust block further chaining at
compile time; Go reports it at serialization (`Validate`, `MarshalRequest`, or `Client.Exec`).

## Scalar terminals: `.count`, `.exists`, `.id`, `.label`

The smallest terminals collapse the whole stream into a single value.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { g, readBatch, SourcePredicate } from "@helix-db/helix-db";

  const aliceStats = readBatch()
    .varAs("post_count", g().nWithLabel("Post").count())
    .varAs(
      "alice_exists",
      g().nWhere(SourcePredicate.eq("username", "alice")).exists(),
    )
    .returning(["post_count", "alice_exists"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn alice_stats() -> ReadBatch {
      read_batch()
          .var_as("post_count", g().n_with_label("Post").count())
          .var_as(
              "alice_exists",
              g().n_where(SourcePredicate::eq("username", "alice")).exists(),
          )
          .returning(["post_count", "alice_exists"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func AliceStats() helix.Request {
  	return helix.ReadQuery("alice_stats").
  		VarAs("post_count", helix.G().NWithLabel("Post").Count()).
  		VarAs("alice_exists", helix.G().NWhere(helix.SourceEq("username", "alice")).Exists()).
  		Returning("post_count", "alice_exists")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "post_count",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "Post" }] } },
            "Count"
          ],
          "condition": null
        }
      },
      {
        "Query": {
          "name": "alice_exists",
          "steps": [
            { "NWhere": { "Eq": ["username", { "String": "alice" }] } },
            "Exists"
          ],
          "condition": null
        }
      }
    ],
    "returns": ["post_count", "alice_exists"]
  }
  ```
</CodeGroup>

The four scalar terminals:

| Step        | Returns                                               |
| ----------- | ----------------------------------------------------- |
| `.count()`  | Number of items in the stream.                        |
| `.exists()` | `true` if the stream is non-empty, `false` otherwise. |
| `.id()`     | Stream of `$id` values, one per item.                 |
| `.label()`  | Stream of `$label` values, one per item.              |

`.id()` and `.label()` are usually most useful followed by another binding via
`.varAs(...)`, or as input to `.project(...)`.

## `.values(...)` and `.valueMap(...)`

When you want a few properties from each item:

* `.values([prop, ...])` — emits an array of arrays, one inner array per item, with
  the properties in the requested order. No keys, only positional values.
* `.valueMap([prop, ...])` — emits an array of objects keyed by property name. Pass
  `null` (or omit the argument) for "every property".

`valueMap` is the default choice for service-facing routes; `values` is handy when
the caller does its own positional decoding.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { g, readBatch } from "@helix-db/helix-db";

  const usersForAdminPanel = readBatch()
    .varAs(
      "rows",
      g()
        .nWithLabel("User")
        .valueMap(["$id", "$label", "username", "tier", "createdAt"]),
    )
    .returning(["rows"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn users_for_admin_panel() -> ReadBatch {
      read_batch()
          .var_as(
              "rows",
              g().n_with_label("User").value_map(Some(vec![
                  "$id",
                  "$label",
                  "username",
                  "tier",
                  "createdAt",
              ])),
          )
          .returning(["rows"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func UsersForAdminPanel() helix.Request {
  	return helix.ReadQuery("users_for_admin_panel").
  		VarAs("rows",
  			helix.G().
  				NWithLabel("User").
  				ValueMap("$id", "$label", "username", "tier", "createdAt"),
  		).
  		Returning("rows")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "rows",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "User" }] } },
            { "ValueMap": ["$id", "$label", "username", "tier", "createdAt"] }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["rows"]
  }
  ```
</CodeGroup>

`$id`, `$label`, `$from`, `$to`, `$distance`, and `$score` are **virtual fields**: they are
always available even though they are not declared on your schema. `$from`/`$to`
appear on edge streams; `$distance`/`$score` appear on ranked vector / text hits.

Filtered `values(...)` and `valueMap(...)` also accept dotted paths into object
properties, such as `metadata.externalID`. `valueMap(null)` returns stored
top-level properties as-is and does not flatten nested objects. When you request a
dotted path explicitly, the returned object is keyed by the requested source
string unless you rename it with `project(...)`.

```ts theme={"languages":{"custom":["languages/helixql.json"]}}
g().nWithLabel("User").valueMap(["$id", "metadata.externalID"]);
```

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().n_with_label("User")
    .value_map(Some(vec!["$id", "metadata.externalID"]));
```

```go theme={"languages":{"custom":["languages/helixql.json"]}}
helix.G().NWithLabel("User").ValueMap("$id", "metadata.externalID")
```

## `.project(...)` with renames and expressions

`project([...])` is the most flexible terminal — it accepts a list of items, each one
either:

* `PropertyProjection.new(prop)` — emit `prop` under its own name.
* `PropertyProjection.renamed(source, alias)` — emit `source` under a different key.
* `ExprProjection.new(alias, expr)` — emit a computed expression under `alias`.

Use it when you want to flatten ids out from under `$id`, return a different name
than the schema's, or compute a derived field.

`PropertyProjection` and `Expr.prop(...)` use the same property lookup rules as
filters. This means nested object fields can be projected and renamed:

```ts theme={"languages":{"custom":["languages/helixql.json"]}}
g().nWithLabel("User").project([
  PropertyProjection.renamed("metadata.externalID", "external_id"),
]);
```

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().n_with_label("User").project(vec![
    PropertyProjection::renamed("metadata.externalID", "external_id"),
]);
```

```go theme={"languages":{"custom":["languages/helixql.json"]}}
helix.G().NWithLabel("User").Project(
	helix.ProjectPropAs("metadata.externalID", "external_id"),
)
```

Dotted paths are also valid in fallback ordering, for example
`.orderBy("metadata.score", Order.Desc)`, but they are not backed by secondary
indexes in V1.

A typical `project` call mixes plain property pulls with one or two renames:

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { g, PropertyProjection, readBatch } from "@helix-db/helix-db";

  const userCards = readBatch()
    .varAs(
      "cards",
      g()
        .nWithLabel("User")
        .project([
          PropertyProjection.renamed("$id", "id"),
          PropertyProjection.new("username"),
          PropertyProjection.new("tier"),
        ]),
    )
    .returning(["cards"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn user_cards() -> ReadBatch {
      read_batch()
          .var_as(
              "cards",
              g().n_with_label("User").project(vec![
                  PropertyProjection::renamed("$id", "id"),
                  PropertyProjection::new("username"),
                  PropertyProjection::new("tier"),
              ]),
          )
          .returning(["cards"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func UserCards() helix.Request {
  	return helix.ReadQuery("user_cards").
  		VarAs("cards",
  			helix.G().
  				NWithLabel("User").
  				Project(
  					helix.ProjectPropAs("$id", "id"),
  					helix.ProjectPropAs("username", "username"),
  					helix.ProjectPropAs("tier", "tier"),
  				),
  		).
  		Returning("cards")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "cards",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "User" }] } },
            {
              "Project": [
                { "source": "$id",      "alias": "id" },
                { "source": "username", "alias": "username" },
                { "source": "tier",     "alias": "tier" }
              ]
            }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["cards"]
  }
  ```
</CodeGroup>

Each entry in `Project` is a bare object with `source` / `alias` (for
`PropertyProjection`) or `alias` / `expr` (for `ExprProjection`). There is no enum
tag around them — the `Projection` enum is untagged on the wire.

### Expressions for `ExprProjection`

`Expr` is a small arithmetic-and-conditionals algebra:

* Sources: `Expr.prop(name)`, `Expr.val(value)`, `Expr.id()`, `Expr.timestamp()`,
  `Expr.datetime()`, `Expr.param(name)`.
* Arithmetic: `.add(other)`, `.sub(other)`, `.mul(other)`, `.div(other)`,
  `.modulo(other)`, `.neg()`.
* Branching: `Expr.case([[predicate, expr], ...], elseExpr)`.

A small derived field — fall back to `"unknown"` when the `tier` property is null:

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import {
    Expr,
    ExprProjection,
    g,
    Predicate,
    PropertyProjection,
    readBatch,
  } from "@helix-db/helix-db";

  const userCardsWithFallback = readBatch()
    .varAs(
      "cards",
      g()
        .nWithLabel("User")
        .project([
          PropertyProjection.renamed("$id", "id"),
          PropertyProjection.new("username"),
          ExprProjection.new(
            "tier",
            Expr.case(
              [[Predicate.isNotNull("tier"), Expr.prop("tier")]],
              Expr.val("unknown"),
            ),
          ),
        ]),
    )
    .returning(["cards"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn user_cards_with_fallback() -> ReadBatch {
      read_batch()
          .var_as(
              "cards",
              g().n_with_label("User").project(vec![
                  PropertyProjection::renamed("$id", "id"),
                  PropertyProjection::new("username"),
                  ExprProjection::new(
                      "tier",
                      Expr::case(
                          vec![(
                              Predicate::is_not_null("tier"),
                              Expr::prop("tier"),
                          )],
                          Some(Expr::val("unknown")),
                      ),
                  ),
              ]),
          )
          .returning(["cards"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func UserCardsWithFallback() helix.Request {
  	unknown := helix.ExprVal("unknown")

  	return helix.ReadQuery("user_cards_with_fallback").
  		VarAs("cards",
  			helix.G().
  				NWithLabel("User").
  				Project(
  					helix.ProjectPropAs("$id", "id"),
  					helix.ProjectPropAs("username", "username"),
  					helix.ProjectExpr("tier", helix.ExprCase(
  						[]helix.CaseBranch{{When: helix.PredIsNotNull("tier"), Then: helix.ExprProp("tier")}},
  						&unknown,
  					)),
  				),
  		).
  		Returning("cards")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "cards",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "User" }] } },
            {
              "Project": [
                { "source": "$id",      "alias": "id" },
                { "source": "username", "alias": "username" },
                {
                  "alias": "tier",
                  "expr": {
                    "Case": {
                      "when_then": [
                        [
                          { "IsNotNull": "tier" },
                          { "Property": "tier" }
                        ]
                      ],
                      "else_expr": { "Constant": { "String": "unknown" } }
                    }
                  }
                }
              ]
            }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["cards"]
  }
  ```
</CodeGroup>

For time-relative comparisons against "now" (e.g. "rows newer than 30 days"), use
`Expr.timestamp()` (server epoch millis) or `Expr.datetime()` (typed datetime) on
one side of a `Predicate.compare(left, CompareOp.*, right)`. Arithmetic on `Expr`
returns another `Expr`, so you can chain `Expr.timestamp().sub(Expr.val(30 * 86_400 * 1000))`
to compute a cut-off server-side.

## Edge property output

For edge streams, `.edgeProperties()` emits each edge as an object with all of its
properties plus the virtual `$id`, `$label`, `$from`, `$to`, `$distance`, and
`$score` fields when those fields are present on the current ranked edge stream.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { g, NodeRef, readBatch, SourcePredicate } from "@helix-db/helix-db";

  const aliceFollowsEdges = readBatch()
    .varAs("user", g().nWhere(SourcePredicate.eq("username", "alice")))
    .varAs(
      "edges",
      g().n(NodeRef.var("user")).outE("FOLLOWS").edgeProperties(),
    )
    .returning(["edges"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn alice_follows_edges() -> ReadBatch {
      read_batch()
          .var_as("user", g().n_where(SourcePredicate::eq("username", "alice")))
          .var_as(
              "edges",
              g().n(NodeRef::var("user")).out_e(Some("FOLLOWS")).edge_properties(),
          )
          .returning(["edges"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func AliceFollowsEdges() helix.Request {
  	return helix.ReadQuery("alice_follows_edges").
  		VarAs("user", helix.G().NWhere(helix.SourceEq("username", "alice"))).
  		VarAs("edges", helix.G().N(helix.NodeVar("user")).OutE("FOLLOWS").EdgeProperties()).
  		Returning("edges")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "user",
          "steps": [
            { "NWhere": { "Eq": ["username", { "String": "alice" }] } }
          ],
          "condition": null
        }
      },
      {
        "Query": {
          "name": "edges",
          "steps": [
            { "N":    { "Var": "user" } },
            { "OutE": "FOLLOWS" },
            "EdgeProperties"
          ],
          "condition": null
        }
      }
    ],
    "returns": ["edges"]
  }
  ```
</CodeGroup>

When you only need a small edge-facing shape, prefer `project(...)` on the edge
stream instead of `edgeProperties()`. Endpoint-qualified projection sources read
properties from the source or target node without traversing to each endpoint:

* `$from.<property>` reads `<property>` from the edge's source node.
* `$to.<property>` reads `<property>` from the edge's target node.

This keeps one result row per edge and fetches only the requested edge and
endpoint properties. `edgeProperties()` still returns full edge properties and
uses `$from` / `$to` for the internal endpoint node ids.

The raw JSON form is a normal `Project` entry, for example
`{"source":"$from.resource_id","alias":"from_id"}` and
`{"source":"$to.resource_id","alias":"to_id"}`.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { g, Projection, readBatch } from "@helix-db/helix-db";

  const relationships = readBatch()
    .varAs(
      "relationships",
      g()
        .eWithLabel("DESCRIBES")
        .project([
          Projection.fromEndpoint("resource_id", "from_id"),
          Projection.toEndpoint("resource_id", "to_id"),
          Projection.property("$id", "edge_id"),
          Projection.property("confidence", "confidence"),
        ]),
    )
    .returning(["relationships"]);
  ```

  ```python Python theme={"languages":{"custom":["languages/helixql.json"]}}
  from helixdb import Projection, g, read_batch


  def list_describes_relationships():
      return (
          read_batch()
          .var_as(
              "relationships",
              g()
              .e_with_label("DESCRIBES")
              .project([
                  Projection.from_endpoint("resource_id", "from_id"),
                  Projection.to_endpoint("resource_id", "to_id"),
                  Projection.property("$id", "edge_id"),
                  Projection.property("confidence", "confidence"),
              ]),
          )
          .returning(["relationships"])
      )
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn list_describes_relationships() -> ReadBatch {
      read_batch()
          .var_as(
              "relationships",
              g().e_with_label("DESCRIBES").project(vec![
                  Projection::from_endpoint("resource_id", "from_id"),
                  Projection::to_endpoint("resource_id", "to_id"),
                  Projection::property("$id", "edge_id"),
                  Projection::property("confidence", "confidence"),
              ]),
          )
          .returning(["relationships"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func ListDescribesRelationships() helix.Request {
  	return helix.ReadQuery("list_describes_relationships").
  		VarAs("relationships",
  			helix.G().
  				EWithLabel("DESCRIBES").
  				Project(
  					helix.ProjectFromEndpoint("resource_id", "from_id"),
  					helix.ProjectToEndpoint("resource_id", "to_id"),
  					helix.ProjectPropAs("$id", "edge_id"),
  					helix.ProjectPropAs("confidence", "confidence"),
  				),
  		).
  		Returning("relationships")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "relationships",
          "steps": [
            { "EWhere": { "Eq": ["$label", { "String": "DESCRIBES" }] } },
            {
              "Project": [
                { "source": "$from.resource_id", "alias": "from_id" },
                { "source": "$to.resource_id",   "alias": "to_id" },
                { "source": "$id",               "alias": "edge_id" },
                { "source": "confidence",        "alias": "confidence" }
              ]
            }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["relationships"]
  }
  ```
</CodeGroup>

## Row bindings: `.projectBindings` for multi-hop correlation

Every terminal above projects from the **final** stream. When one output row must
combine values captured at **different hops** of the same traversal, use row
bindings: tag elements with `.bind(name)` as the traversal passes them, then build
the rows with `.projectBindings([...])` (keeps duplicates) or
`.projectDistinctBindings([...])` (dedups identical rows).

Each entry is a `BindingProjection`:

* `BindingProjection.binding(name, source, alias)` — read `source` from a named
  binding.
* `BindingProjection.current(source, alias)` — read `source` from the current
  element.
* `BindingProjection.coalesce([ref, ...], alias)` — first present non-null
  reference wins, where each `ref` is `BindingProjection.bindingRef(name, source)`
  or `BindingProjection.currentRef(source)`.

`source` accepts the same stored properties and virtual fields (`$id`, `$label`,
`$from`, `$to`, `$distance`, `$score`) as `.project(...)`.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { BindingProjection, g, readBatch, sub } from "@helix-db/helix-db";

  const orders = readBatch()
    .varAs(
      "rows",
      g()
        .nWithLabel("Customer")
        .bind("customer")
        .out("PLACED").bind("order")
        .projectDistinctBindings([
          BindingProjection.binding("customer", "$id", "customer_id"),
          BindingProjection.binding("customer", "name", "customer_name"),
          BindingProjection.binding("order", "$id", "order_id"),
          BindingProjection.binding("order", "total", "order_total"),
        ]),
    )
    .returning(["rows"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn orders() -> ReadBatch {
      read_batch()
          .var_as(
              "rows",
              g().n_with_label("Customer")
                  .bind("customer")
                  .out(Some("PLACED")).bind("order")
                  .project_distinct_bindings(vec![
                      BindingProjection::binding("customer", "$id", "customer_id"),
                      BindingProjection::binding("customer", "name", "customer_name"),
                      BindingProjection::binding("order", "$id", "order_id"),
                      BindingProjection::binding("order", "total", "order_total"),
                  ]),
          )
          .returning(["rows"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func Orders() helix.Request {
  	return helix.ReadQuery("orders").
  		VarAs("rows",
  			helix.G().
  				NWithLabel("Customer").
  				Bind("customer").
  				Out("PLACED").Bind("order").
  				ProjectDistinctBindings(
  					helix.ProjectNamedBinding("customer", "$id", "customer_id"),
  					helix.ProjectNamedBinding("customer", "name", "customer_name"),
  					helix.ProjectNamedBinding("order", "$id", "order_id"),
  					helix.ProjectNamedBinding("order", "total", "order_total"),
  				),
  		).
  		Returning("rows")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "rows",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "Customer" }] } },
            { "Bind": "customer" },
            { "Out": "PLACED" },
            { "Bind": "order" },
            { "ProjectBindings": {
              "projections": [
                { "kind": "Property", "target": { "Binding": "customer" }, "source": "$id", "alias": "customer_id" },
                { "kind": "Property", "target": { "Binding": "customer" }, "source": "name", "alias": "customer_name" },
                { "kind": "Property", "target": { "Binding": "order" }, "source": "$id", "alias": "order_id" },
                { "kind": "Property", "target": { "Binding": "order" }, "source": "total", "alias": "order_total" }
              ],
              "distinct": true
            } }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["rows"]
  }
  ```
</CodeGroup>

<Note>
  Row bindings are available in the TypeScript, Rust, and Go SDKs but **not** in the
  Python SDK yet — from Python, hand-write the `Bind` / `ProjectBindings` JSON AST.
  See [Advanced traversals](/database/querying-guide/advanced) for a fuller example
  that correlates across `union` and `optional` branches with `coalesce`.
</Note>

## Aggregations: `.group`, `.groupCount`, `.aggregateBy`

For grouped counts and aggregate statistics:

* `.group(prop)` — bucket the stream by `prop`, emit `{groupValue: [items...]}`.
* `.groupCount(prop)` — bucket and count, emit `{groupValue: count}`.
* `.aggregateBy(AggregateFunction.*, prop)` — apply a reducer to one property:
  `Count`, `Sum`, `Min`, `Max`, `Mean`.

<CodeGroup>
  ```ts TypeScript theme={"languages":{"custom":["languages/helixql.json"]}}
  import { AggregateFunction, g, readBatch } from "@helix-db/helix-db";

  const tierBreakdown = readBatch()
    .varAs("by_tier", g().nWithLabel("User").groupCount("tier"))
    .varAs("avg_post_age", g().nWithLabel("Post").aggregateBy(AggregateFunction.Mean, "createdAt"))
    .returning(["by_tier", "avg_post_age"]);
  ```

  ```rust Rust theme={"languages":{"custom":["languages/helixql.json"]}}
  use helix_db::dsl::prelude::*;

  #[register]
  pub fn tier_breakdown() -> ReadBatch {
      read_batch()
          .var_as("by_tier", g().n_with_label("User").group_count("tier"))
          .var_as(
              "avg_post_age",
              g().n_with_label("Post").aggregate_by(AggregateFunction::Mean, "createdAt"),
          )
          .returning(["by_tier", "avg_post_age"])
  }
  ```

  ```go Go theme={"languages":{"custom":["languages/helixql.json"]}}
  import helix "github.com/helixdb/helix-db/sdks/go"

  func TierBreakdown() helix.Request {
  	return helix.ReadQuery("tier_breakdown").
  		VarAs("by_tier", helix.G().NWithLabel("User").GroupCount("tier")).
  		VarAs("avg_post_age", helix.G().NWithLabel("Post").AggregateBy(helix.AggregateMean, "createdAt")).
  		Returning("by_tier", "avg_post_age")
  }
  ```

  ```json JSON theme={"languages":{"custom":["languages/helixql.json"]}}
  {
    "queries": [
      {
        "Query": {
          "name": "by_tier",
          "steps": [
            { "NWhere":     { "Eq": ["$label", { "String": "User" }] } },
            { "GroupCount": "tier" }
          ],
          "condition": null
        }
      },
      {
        "Query": {
          "name": "avg_post_age",
          "steps": [
            { "NWhere":      { "Eq": ["$label", { "String": "Post" }] } },
            { "AggregateBy": ["Mean", "createdAt"] }
          ],
          "condition": null
        }
      }
    ],
    "returns": ["by_tier", "avg_post_age"]
  }
  ```
</CodeGroup>

`AggregateFunction` values are serialized as strings: `"Count"`, `"Sum"`, `"Min"`,
`"Max"`, `"Mean"`.

## Next Steps

<CardGroup cols={2}>
  <Card title="Mutations" icon="pen-to-square" href="/database/querying-guide/mutations">
    `writeBatch`, `addN`, `addE`, `setProperty`, `drop` — and using results inside the same batch.
  </Card>

  <Card title="Vector and text search" icon="magnifying-glass" href="/database/querying-guide/search">
    `vectorSearchNodes`, `textSearchNodes`, projecting `$distance`, follow-on traversal.
  </Card>
</CardGroup>
