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

# Query and indexes

> Use read-only SQL and table indexes.

ZangTable exposes a read-only SQL query endpoint for reporting, lookups, joins, and debugging data relationships. Index endpoints help speed up common filters and sorts, especially when a table starts getting larger.

Use the query endpoint when you want to answer questions like:

* "Which members have overdue library books?"
* "Which customers have open invoices?"
* "Which orders should be merged with shipment rows by `order_id`?"
* "What query shape should I turn into a dedicated app endpoint later?"

<Warning>
  The query endpoint is read-only. Use record and schema endpoints for writes.
</Warning>

## Read-only query

```bash theme={null}
curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT id, title FROM books WHERE status = ?","params":["late"]}'
```

Example response:

```json theme={null}
{
  "ok": true,
  "data": {
    "rows": [
      {"id": 101, "title": "The Pragmatic Programmer"},
      {"id": 102, "title": "Clean Architecture"}
    ],
    "row_count": 2,
    "limit": 500,
    "duration_ms": 4
  },
  "error": null
}
```

## Relational example: find members with late library books

First, fetch late loans:

```bash theme={null}
curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT member_id, book_id, due_date FROM loans WHERE returned_at IS NULL AND due_date < ?","params":["2026-06-01"]}'
```

```json theme={null}
{
  "ok": true,
  "data": {
    "rows": [
      {"member_id": 7, "book_id": 101, "due_date": "2026-05-20"},
      {"member_id": 9, "book_id": 102, "due_date": "2026-05-22"}
    ],
    "row_count": 2
  },
  "error": null
}
```

Then fetch the matching members:

```bash theme={null}
curl -X POST "$BASE_URL/projects/$PROJECT/query" \
  -H "Authorization: Bearer $ZANGTABLE_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"sql":"SELECT id, email, name FROM members WHERE id IN (?, ?)","params":[7,9]}'
```

```json theme={null}
{
  "ok": true,
  "data": {
    "rows": [
      {"id": 7, "email": "ada@example.com", "name": "Ada"},
      {"id": 9, "email": "grace@example.com", "name": "Grace"}
    ],
    "row_count": 2
  },
  "error": null
}
```

Your backend can merge those results by `member_id` / `id` and send reminder emails to members with late books. Once the pattern is stable, you can also run it as one SQL query with a join:

```sql theme={null}
SELECT members.id, members.email, members.name, loans.book_id, loans.due_date
FROM members
JOIN loans ON loans.member_id = members.id
WHERE loans.returned_at IS NULL AND loans.due_date < ?
```

## Indexes

Use indexes when the same columns power frequent filters, sorts, joins, or dashboard views. For example, if you often query late loans by `returned_at` and `due_date`, create an index on those columns so SQLite can find matching rows faster.

<CardGroup cols={2}>
  <Card title="Run query" icon="database" href="/reference/query/query-records" />

  <Card title="List indexes" icon="list" href="/reference/query/list-indexes" />

  <Card title="Create index" icon="plus" href="/reference/query/create-index" />

  <Card title="Delete index" icon="trash" href="/reference/query/delete-index" />
</CardGroup>
