# Querying

Build SELECT queries in Surqlize with typed fields, where clauses, ordering, projections, and the advanced SurrealQL SELECT clauses, then execute or compile them.

Surqlize builds a `SELECT` as a typed query object. You start it from a model, refine it with chained methods, and either compile it to a SurrealQL string or execute it through the [SDK executor](/docs/reference/php/libraries/surqlize/connections.md).

```php
$query = User::select(fn ($user) => [$user->name, $user->age])
    ->where(fn ($user) => $user->age->gte(18))
    ->orderBy(fn ($user) => $user->name->asc());

$sql = $query->compile();
// SELECT name, age FROM user WHERE age >= 18 ORDER BY name ASC

$users = $query->collectModels();
```

The closures receive a typed field set, so field names, conditions, and projections are checked against your model. You can also pass plain strings where you prefer.

## Selecting fields

Pass a closure that returns the fields to select, or an array of field names. `query()` is shorthand for `SELECT *`.

```php
User::select(fn ($user) => [$user->name, $user->age])->compile();
// SELECT name, age FROM user

User::select(['name', 'age'])->compile();
// SELECT name, age FROM user

User::query()->compile();
// SELECT * FROM user
```

## Where clauses

A `where()` closure returns one predicate, or a list of predicates that are combined with `AND`.

```php
User::select(fn ($user) => [$user->name])
    ->where(fn ($user) => $user->age->gte(18))
    ->compile();
// SELECT name FROM user WHERE age >= 18

User::select(fn ($user) => [$user->name])
    ->where(fn ($user) => [
        $user->name->eq('beau'),
        $user->age->gte(18),
    ])
    ->compile();
// SELECT name FROM user WHERE name = "beau" AND age >= 18
```

Field helpers map to SurrealQL operators.

| Helper | Operator |
|--------|----------|
| `eq($value)` | `=` |
| `notEq($value)` | `!=` |
| `gt($value)` | `>` |
| `gte($value)` | `>=` |
| `lt($value)` | `<` |
| `lte($value)` | `<=` |
| `includes($value)` | `INCLUDES` |
| `contains($value)` | `CONTAINS` |
| `like($value)` | `LIKE` |
| `condition($operator, $value)` | A custom operator |

## Ordering

`orderBy()` accepts a field helper, or a field plus a direction.

```php
User::query()->orderBy(fn ($user) => $user->name->asc())->compile();
// SELECT * FROM user ORDER BY name ASC

User::query()->orderBy(fn ($user) => $user->name, 'DESC')->compile();
// SELECT * FROM user ORDER BY name DESC
```

## Fetching links

`fetch()` resolves record links so related records are returned inline.

```php
User::select(fn ($user) => [$user->name])
    ->fetch(fn ($user) => $user->address)
    ->compile();
// SELECT name FROM user FETCH address
```

## Pagination

`page()` sets a page and page size. `limit()` and `start()` give the same control directly.

```php
User::query()->page(page: 3, perPage: 25)->compile();
// SELECT * FROM user LIMIT 25 START 50

User::query()->limit(25)->start(50);
```

## Projections and aggregates

Combine projection helpers with `groupBy()` to aggregate.

```php
use Surqlize\Query\Fields\Projection;

User::select(fn ($user) => [
        $user->age,
        Projection::count()->as('total'),
    ])
    ->groupBy(fn ($user) => $user->age)
    ->orderBy('total', 'DESC')
    ->compile();
// SELECT age, count() AS total FROM user GROUP BY age ORDER BY total DESC
```

The available helpers are `Projection::count()`, `Projection::sum($field)`, `Projection::mean($field)`, and `Projection::raw($expression)`. Chain `->as('alias')` to name the result.

## Advanced SELECT clauses

Surqlize supports the wider set of SurrealQL `SELECT` clauses, compiled in the correct order.

```php
User::select(['*'])
    ->omit('password')
    ->withIndex('idx_user_email')
    ->where(fn ($user) => $user->age->gte(18))
    ->split('tags')
    ->orderBy(fn ($user) => $user->name->desc())
    ->limit(10)
    ->start(20)
    ->fetch(fn ($user) => $user->address)
    ->timeout(5)
    ->tempFiles()
    ->explain(full: true)
    ->compile();
// SELECT * OMIT password FROM user WITH INDEX idx_user_email WHERE age >= 18
// SPLIT tags ORDER BY name DESC LIMIT 10 START 20 FETCH address
// TIMEOUT 5s TEMPFILES EXPLAIN FULL
```

Other clause helpers include `withoutIndex()`, `groupAll()`, and `withoutFrom()`. `timeout()` takes an amount and an optional unit (`s` by default).

## Selecting values

`selectValue()` builds a `SELECT VALUE` query that returns scalar rows.

```php
$name = User::selectValue(fn ($user) => $user->name)
    ->where(fn ($user) => $user->age->gte(18))
    ->first();
```

`SELECT VALUE` rows are scalars and cannot be hydrated with `collectModels()`.

## Executing a query

| Method | Result |
|--------|--------|
| `compile()` | A literal SurrealQL string, for debugging and tests |
| `toBoundQuery()` | An SDK [`BoundQuery`](/docs/reference/php/v2/api/utilities.md#boundquery) with parameter-bound values |
| `collect()` | A list of raw rows |
| `collectModels()` | A list of hydrated models |
| `lazyModels()` | A generator of hydrated models |
| `first()` | The first scalar or model, depending on the query |
| `explainPlan()` | The raw rows from an `EXPLAIN` query |

```php
$users = User::query()
    ->where(fn ($user) => $user->age->gte(18))
    ->collectModels();

foreach ($users as $user) {
    echo $user->name;
}
```

> [!NOTE]
> `compile()` produces a literal string for inspection and deterministic tests. Runtime execution uses `toBoundQuery()` under the hood, so values are sent as bound parameters rather than interpolated into the query text.

## Learn more

- [Mutations](/docs/reference/php/libraries/surqlize/mutations.md) for create, update, and delete
- [Edges and graph](/docs/reference/php/libraries/surqlize/edges-and-graph.md) for graph traversal in a select
- [Search, vector, and geometry](/docs/reference/php/libraries/surqlize/search-vector-geometry.md) for specialised field helpers
