> ## Documentation Index
> Fetch the complete documentation index at: https://cindel.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Projections and Aggregates for Efficient Data Access

> Read only the fields you need with Cindel's property projections, and compute counts, min, max, sum, and average directly from query results.

Most queries return full typed objects, and that is the right choice whenever your app needs to display, edit, or write those objects back. Projections are different: instead of hydrating complete records, they read only the fields you ask for. Aggregates go one step further and calculate a single summary value — a count, minimum, maximum, sum, or average — without reading any individual records into application code at all. Use projections to keep lightweight lists and autocomplete fast; use aggregates for dashboard summaries and numeric totals.

## Single-Property Projections

Generated property helpers project one persisted field from a query result. Call the helper after your query conditions and execute with `findAll()` or `findFirst()`.

```dart theme={null}
// Returns List<String>
final titles = await db.todos
    .all()
    .titleProperty()
    .findAll();

for (final title in titles) {
  print(title);
}
```

Use `findFirst()` when only the first projected value is needed:

```dart theme={null}
final firstTitle = await db.todos
    .all()
    .titleProperty()
    .findFirst();
```

Combine with normal query conditions, sorting, and pagination just as you would with a full-object query:

```dart theme={null}
final openTitles = await db.todos
    .filter()
    .completedEqualTo(false)
    .sortByCreatedAt(order: CindelSortOrder.descending)
    .titleProperty()
    .findAll();
```

### Dynamic Property Projection

When the field name is only known at runtime, use the lower-level `property<T>` API:

```dart theme={null}
final titles = await db.todos
    .all()
    .property<String>('title')
    .findAll();
```

Prefer generated property helpers whenever the field is known at compile time. Use `property<T>('fieldName')` only for dynamic code that already works with persisted field names.

## Multi-Property Projections

Use `properties([...])` to project several persisted fields at once. The result is a `List<CindelDocument>`, where each entry is a `Map<String, Object?>` keyed by the persisted field names you requested.

```dart theme={null}
// Returns List<CindelDocument>  (typedef CindelDocument = Map<String, Object?>)
final rows = await db.todos
    .all()
    .properties(['dbId', 'title'])
    .findAll();

for (final row in rows) {
  final id    = row['dbId']  as int;
  final title = row['title'] as String;

  print('$id: $title');
}
```

Multi-property projections are a good fit for table views, autocomplete lists, export previews, and summary screens that only need a handful of fields per record.

<Note>
  Pass persisted field names to `properties([...])`. If a model field is annotated with `@Name`, its persisted name may differ from the Dart field name. Generated single-property helpers handle this automatically when the field is known at compile time.
</Note>

## Aggregates

Aggregate helpers are available on any property query. Chain them in place of `findAll()` or `findFirst()` to receive a single computed value.

| Method      | Returns   | Use for                                        |
| ----------- | --------- | ---------------------------------------------- |
| `count()`   | `int`     | Number of non-null projected values            |
| `min()`     | `T?`      | Smallest comparable value (e.g. earliest date) |
| `max()`     | `T?`      | Largest comparable value (e.g. latest date)    |
| `sum()`     | `num`     | Numeric total                                  |
| `average()` | `double?` | Numeric mean                                   |

### `count()`

```dart theme={null}
final datedCount = await db.todos
    .all()
    .createdAtProperty()
    .count();
```

`count()` counts non-null projected values. Use query-level `count()` (without a property) when you want to count the matching objects themselves:

```dart theme={null}
final openCount = await db.todos
    .filter()
    .completedEqualTo(false)
    .count();
```

### `min()` and `max()`

`min()` and `max()` require comparable values (dates, numbers, strings):

```dart theme={null}
final oldest = await db.todos
    .all()
    .createdAtProperty()
    .min();

final newest = await db.todos
    .all()
    .createdAtProperty()
    .max();
```

### `sum()` and `average()`

`sum()` and `average()` require numeric values:

```dart theme={null}
final revenue = await db.orders
    .all()
    .totalCentsProperty()
    .sum();

final averageRevenue = await db.orders
    .all()
    .totalCentsProperty()
    .average();
```

### Combining Aggregates with Filters

All aggregates compose naturally with filters. Apply conditions before the property projection:

```dart theme={null}
final openEstimate = await db.todos
    .filter()
    .completedEqualTo(false)
    .estimateMinutesProperty()
    .sum();

final completedRevenue = await db.orders
    .filter()
    .paidEqualTo(true)
    .totalCentsProperty()
    .sum();
```

<Tip>
  Combine projections and aggregates with `where()` filters for best performance. Starting from an indexed lookup narrows the candidate set before the projection or aggregate runs.
</Tip>

## Common Use Cases

<CodeGroup>
  ```dart Lightweight id + title list theme={null}
  final rows = await db.todos
      .all()
      .sortByTitle()
      .properties(['dbId', 'title'])
      .findAll();

  for (final row in rows) {
    final id    = row['dbId']  as int;
    final title = row['title'] as String;
    print('$id: $title');
  }
  ```

  ```dart Autocomplete suggestions theme={null}
  final titles = await db.todos
      .filter()
      .titleContains(search)
      .titleProperty()
      .findAll();
  ```

  ```dart Dashboard count theme={null}
  final openTodos = await db.todos
      .filter()
      .completedEqualTo(false)
      .count();
  ```

  ```dart Numeric total theme={null}
  final totalCents = await db.orders
      .filter()
      .paidEqualTo(true)
      .totalCentsProperty()
      .sum();
  ```

  ```dart Date range (oldest / newest) theme={null}
  final oldest = await db.todos
      .all()
      .createdAtProperty()
      .min();

  final newest = await db.todos
      .all()
      .createdAtProperty()
      .max();
  ```
</CodeGroup>

## Choosing the Right Query Shape

Use this decision guide to pick the right approach for each screen or operation:

| Need                                         | Approach                                                      |
| -------------------------------------------- | ------------------------------------------------------------- |
| Display, edit, or write objects back         | Full object query — `findAll()` / `findFirst()`               |
| List or table needing only a few fields      | Multi-property projection — `properties([...])`               |
| Autocomplete or label list needing one field | Single-property projection — `titleProperty().findAll()`      |
| Count, total, min, max, or average           | Aggregate — `count()`, `sum()`, `min()`, `max()`, `average()` |

```dart theme={null}
// Full objects — display and edit
final todos = await db.todos.all().findAll();

// Single-property projection — labels only
final titles = await db.todos.all().titleProperty().findAll();

// Aggregate — summary value only
final count = await db.todos.filter().completedEqualTo(false).count();
```

`CindelDocument` (`Map<String, Object?>`) is the map-shaped type returned by multi-property projections. Most application code should prefer typed objects and generated helpers unless you are intentionally working with a partial projection.
