# First queries

> A first SQL session — an append table, a versioned table with attribute groups, a read AS OF COMMIT, a streaming query and a transformation.

This walkthrough builds the two tables used throughout the docs: `transfers`
and `balances`. It assumes a running node; see
[Install](/docs/getting-started/install).

## Sending a statement

Every statement is sent on its own, in one of two ways:

- HTTP `POST /sql`, with a JSON body whose `sql` field holds the statement and
  whose `maximum_rows` field caps the rows returned. A read answers with a
  result document of type `nyxdb.query-result/2`.
- A WebSocket session on `/client` (subprotocol `nyxdb.public.v1`), which sends
  the statement in an `ExecuteSql` frame. Streaming queries need this route.

Both need a session obtained from `POST /auth/session`. The request and frame
formats are on [Protocol](/docs/drivers/protocol); the
[console](/docs/getting-started/console) sends statements for you.

## 1. An append table

An append table keeps every row. It carries no primary key.

```sql
CREATE TABLE transfers (
  chain_id UINT16 NOT NULL,
  token STRING NOT NULL,
  sender STRING NOT NULL,
  recipient STRING NOT NULL,
  amount UINT256 NOT NULL,
  transferred_at DATETIME64(3, 'UTC') NOT NULL,
  _index UINT128 NOT NULL,
  INDEX transfers_ts (transferred_at) USING MINMAX SCOPE HISTORY GRANULE 8192
)
MODE APPEND
SHARD BY (token) SHARDS AUTO
PARTITION BY MONTH(transferred_at)
ORDER BY (chain_id, token, _index)
STORAGE PROFILE COLUMNAR;
```

```sql
INSERT INTO transfers (
  chain_id, token, sender, recipient, amount, transferred_at, _index
)
VALUES (
  1, 'USDC', '0x4b1e7c2a', '0x9c2e4f1a', 2500000000, '2026-10-01 12:00:00.000',
  1001
);
```

Each `INSERT` carries one row. Bulk loads use the batch routes on
[Protocol](/docs/drivers/protocol).

## 2. A versioned table with attribute groups

A versioned table keeps the current row per key and every accepted version. Its
attribute groups let several producers write one row.

```sql
CREATE TABLE balances (
  chain_id UINT16 NOT NULL,
  address STRING NOT NULL,
  token STRING NOT NULL,
  amount UINT256 NULL,
  updated_at DATETIME64(3, 'UTC') NULL,
  balance_index UINT128 NULL,
  balance_version UINT64 NULL,
  transfer_count UINT64 NULL,
  last_transfer_at DATETIME64(3, 'UTC') NULL,
  activity_index UINT128 NULL,
  PRIMARY KEY (chain_id, address, token),
  ATTRIBUTE GROUP holding (amount, updated_at, balance_index, balance_version)
    VALID BY (balance_index)
    REVISION BY (balance_version),
  ATTRIBUTE GROUP activity (transfer_count, last_transfer_at, activity_index)
    VALID BY (activity_index)
)
MODE VERSIONED
SHARD BY (address) SHARDS AUTO
ORDER BY (chain_id, address, token);
```

```sql
UPSERT INTO balances (
  chain_id, address, token, amount, updated_at, balance_index, balance_version
)
VALUES (1, '0x9c2e4f1a', 'USDC', 2500000000, '2026-10-01 12:00:00.000', 1001, 1)
ATTRIBUTE GROUP holding;
```

```sql
SELECT address, token, amount, updated_at
FROM balances
WHERE chain_id = 1 AND address = '0x9c2e4f1a';
```

The `ATTRIBUTE GROUP holding` write changes only that group of the row. See
[INSERT](/docs/sql/insert) and [Attribute groups](/docs/concepts/attribute-groups).

## 3. A read at an earlier commit

`AS OF COMMIT` reads a versioned table as it stood at an earlier commit:

```sql
SELECT address, token, amount
FROM balances AS OF COMMIT 42
WHERE chain_id = 1;
```

See [Versioned tables](/docs/concepts/versioned-tables).

## 4. A streaming query

Over a WebSocket session, `STREAM SELECT` returns the current result and then
keeps delivering its changes:

```sql
STREAM SELECT chain_id, token, SUM(amount) AS volume, COUNT(*) AS transfers
FROM transfers
GROUP BY chain_id, token;
```

The stream opens with a snapshot of the totals; each new transfer then arrives
as one change to its token's row. The stream runs until the client cancels it.
See [Streaming queries](/docs/sql/streaming).

## 5. A transformation

A transformation keeps a target up to date from a query over its sources. Here
`balance_activity` maintains the `activity` group of every balance row from the
transfers:

```sql
CREATE TRANSFORMATION balance_activity INTO balances ATTRIBUTE GROUP activity AS
SELECT
  chain_id,
  recipient AS address,
  token,
  COUNT(*) AS transfer_count,
  MAX(transferred_at) AS last_transfer_at,
  MAX(_index) AS activity_index
FROM transfers
GROUP BY chain_id, recipient, token;
```

Every transfer committed from then on updates the recipient's row, while the
`holding` group keeps the values its producer writes. See
[CREATE TRANSFORMATION](/docs/sql/create-transformation).

## Next steps

- [Tables](/docs/concepts/tables) for the three modes.
- [SELECT](/docs/sql/select) for the query grammar.
- [Transformations](/docs/concepts/transformations) for derived tables.
- [Consistency](/docs/concepts/consistency) for what a statement guarantees.

---

Source: https://nyxdb.io/docs/getting-started/first-queries/
