NQL works like jq but with SQL for querying JSON, YAML, TOML, XML, CSV, TSV files. It also lets you use SQL to ship data both ways between files and relational databases. No weird syntax.
NQL treats json & yaml arrays as tables and fields as columns so you can run SQL over a file as if it were a database.
{
"users": [
{"id": 1, "name": "Alice", "active": true},
{"id": 2, "name": "Bob", "active": false},
{"id": 3, "name": "Charlie", "active": true}
],
"address": [
{"user_id": 1, "city": "New York"},
{"user_id": 2, "city": "New York"},
{"user_id": 3, "city": "London"}
]
} Get all the active users from the file:
nql "select name from users where active = true order by id;" users.json
[
{"name":"Alice"},
{"name":"Charlie"}
]
If the file has ID fields it can match on then nql will infer key relationships for SQL joins. You can also change the shape of the output - here we will create a json object 'customer' instead of user with the "into" keyword, and we will merge the user and address blocks together.
nql "select u.id into {customer.id}, u.name into {customer.name},
u.active into {customer.valid}, a.city into {customer.location}
from users u
join address a on a.user_id = u.id
order by u.id;" users.json
[
{"customer": {"id":1,"name":"Alice", "valid":true, "location":"New York"}},
{"customer": {"id":2,"name":"Bob", "valid":false,"location":"New York"}},
{"customer": {"id":3,"name":"Charlie","valid":true, "location":"London"}}
]
(If it can't infer the relationships you'll get a warning and you can tell it explicitly to match fields - see the structure keyword in the manual)
This example inserts data from the users.json file into the 'appusers' table in a local H2 database:
nql "insert into appusers (id, username, active) values ({users.id}, {users.name}, {users.active});" \
users.json --db h2 --database file:./users-db`
More advanced sql just works - you can use a where, you can use subselects etc etc.
This updates matching users in the appusers table from the users.json file:
nql "update appusers set isactive = {users.active} where id = {users.id};" \
users.json --db h2 --database file:./users-db
Deleting data, this time sourcing the input from xml:
echo "<person><id>1</id></person>" | nql \
"delete from person where personid = {person.id};" \
-t xmlThis example pulls data from the customer-demo database into YAML.
The format is controlled either with "output " or with -o <format> on the command line.
nql "output yaml; select id, name, city from customer order by id;" \
--db h2 --database file:./customer-demo -o yaml
-
id: "1"
name: "Alice"
city: "London"
-
id: "2"
name: "Bob"
city: "Bristol"Use the into clause to rename fields or place them into specific places in the output.
select count(*) into {summary.customerCount}, min(name) into {summary.firstCustomer} from customer;
{
"summary": {"customerCount":2,"firstCustomer":"Alice"}
}See the appendix for table details, this example is an ecommerce DB with tables for
customer, address, customer_order, order_item
The query joins customers and their orders together in the DB with standard SQL and puts it into
a different structure in the output json using the into statement:
select
c.name into {customers.customer.name},
a.city into {customers.customer.city} absent on null,
o.id into {customers.customer.orders.order.order_id},
o.ordered_on into {customers.customer.orders.order.date},
i.sku into {customers.customer.orders.order.items.item.product},
i.quantity into {customers.customer.orders.order.items.item.qty}
from customer c
left join address a on a.customer_id = c.id and a.primary_residence = 'Y'
left join customer_order o on o.customer_id = c.id
left join order_item i on i.order_id = o.id
;Note the use of "absent on null" to suppress fields when the value is null. The result contains each customer once and nests orders and items beneath it:
{
"customers": [
{
"customer": {
"name": "Alice", "city": "Tokyo",
"orders": {
"order": [
{
"order_id": 1001, "date": "2026-07-01",
"items": {
"item": [
{ "product": "TEA", "qty": 2 },
{ "product": "CAKE", "qty": 1 }
]
}
},
{
"order_id": 1002, "date": "2026-07-15",
"items": {
"item": { "product": "MUG", "qty": 2 }
}
}
]
}
}
},
{
"customer": {
"name": "Bob", "city": "Sydney",
"orders": {
"order": {
"order_id": 1003, "date": "2026-07-20",
"items": {
"item": {
"product": "COFFEE", "qty": 1
}
}
}
}
}
},
{
"customer": {
"name": "Yuki",
"orders": {}
}
}
]
}When running nql against a database you can also run literal sql commands with the literal keyword.
For instance, create a table...
nql "
literal drop table if exists person;
literal create table person (
personid integer auto_increment primary key,
firstname varchar(80),
lastname varchar(80),
city varchar(80)
);
" --db h2 --database file:./customer-demoAn insert can write database-assigned values back into its input hierarchy.
This XML insert maps the generated key into {person.id}:
nql "output xml;
insert into person (firstname, lastname, city)
values ({person.firstName}, {person.lastName}, {person.city})
returns personid into {person.id};" \
-t xml \
--db h2 \
--database file:./customer-demo <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<person>
<firstName>Alice</firstName>
<lastName>Adams</lastName>
<city>London</city>
</person>
XMLThe returned XML contains the generated ID:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<firstName>Alice</firstName>
<lastName>Adams</lastName>
<city>London</city>
<id>3</id>
</person>The returns keyword is also valid on updates when the database calculates
timestamps, versions, or other values.
Stored-procedure calls, repeated child records, transactions, error policies, and captured query rows are covered in the DML reference.
One more trick - you can directly convert files from one format to another, say JSON to XML, or XML to YAML, any combination of the supported file types:
nql users.json -o csv
nql customers.json -o yaml
nql customers.xml -o json
All of the commands operate on JSON/JSONL/YAML/ToML/XML/CSV/TSV input files and can output to JSON, YAML, XML, TOML, JSONL, CSV, TSV, and Markdown. Beware! At this stage formatting and comments in the files are not preserved.
| Platform | instructions |
|---|---|
| macOS ARM64 | brew install blater/tap/nql |
| Windows x64 | Run from an administrator powershell with Chocolatey installed. irm https://raw.githubusercontent.com/blater/nql/master/util/chocolatey/install.ps1 |
| Lunux x64 | This installs the latest NQL package directly from GitHub Releases. Rerun the same command to upgrade. curl -fsSL https://raw.githubusercontent.com/blater/nql/master/util/install-linux.sh |
NQL uses positional operands for the main inputs and flags for optional changes or explicit disambiguation:
nql [run] <script> [data] [options]
nql <data-file> [options]
nql convert [data] [options]
nql catalog [data] [pattern] [options]
nql cache load [data] [name] [options]
nql cache use <name> [options]
nql cache list [options]
nql cache clear (<name> | olderthan <age> | all) [options]
nql capabilities [-r <format>]
Common flag groups:
--cache [--name <cache-name>]selects persistent cache execution. With data but no script it loads and activates a new cache; with a script it selects the active or named cache without automatically loading the data into it.--cache-dir <path>selects the directory that directly contains cache state, making independent runs easy to isolate.-o, --output <format>selects result-data output;-r, --report-format <format>selects operational reports. Result data defaults to JSON, catalog/cache reports default to Markdown, and the capability contract defaults to JSON.nql capabilities(ornql --capabilities) prints a versioned, side-effect-free discovery contract for agents and integrations.--config <file.properties>supplies operational NQL/JDBC settings.--params-file <file.properties>supplies parameters visible to scripts, and repeatable--param <name=value>entries override individual parameter values.
Run nql help <command> for command-specific syntax and examples.
NQL is intended for work that crosses document and relational boundaries:
- exporting joined database data into a deliberate JSON, YAML, or XML shape;
- applying JSON, YAML, TOML, XML, CSV, TSV, or Parquet data through database DML;
- joining or aggregating related collections inside structured files;
- replacing one-off data movement code with a checked-in SQL-like script.
Use a focused tool when the task stays inside a simpler boundary:
- jq for JSON-native filtering and editing;
- yq or Dasel for direct YAML or document edits;
- Remarshal for guarded format conversion;
- Miller for record-stream processing;
- DuckDB or another SQL-over-file tool for primarily analytical, tabular work.
The comparison guide describes these boundaries in more detail.
- User manual Start here!
- Installation
- Task-oriented recipes
- Automation and CI/CD
- Troubleshooting
- Frequently asked questions
NQL is licensed under the GNU Affero General Public License v3.0.
You can supply all the parameters on the command line
nql myscript.nql \
--db postgresql \
--host db.example.com \
--port 5432 \
--database sales \
--user report_user \
--password secret
or supply an NQL configuration file containing the connection details
nql myscript.nql --config mydatabase.propertiesThe JDBC guide covers this in detail.
Native release builds include drivers for H2, PostgreSQL, MySQL, and MariaDB. Additional driver profiles are available when building from source. The JVM build can also load a driver JAR at runtime.
A query and input file use a temporary local database for that command. Use
cache load explicitly only when imported data should remain available to
later NQL commands:
nql cache load customers.json
nql catalog '*'
nql "select id, name from customers order by id;"The cache reference covers storage, selection, inspection, and cleanup. NQL does not upload source data or send usage telemetry.
Piped, redirected, and here-document stdin is always data, never NQL script text.
It defaults to JSON unless -t, --input-format says otherwise. An explicit data
file or literal input takes precedence; if piped input is immediately detectable,
NQL warns that stdin is being ignored.
Bare nql prints brief usage instead of performing an identity JSON-to-JSON
conversion. Use nql convert, an output option such as nql -o yaml, or a script
when consuming stdin.
Result data and successful command reports are written to stdout. Warnings,
errors, and debug output are written to stderr. --report-format produces
versioned, structured catalog/cache/capability reports and structured diagnostics in the
selected JSON, JSONL, YAML, TOML, XML, CSV, TSV, or Markdown format.
Exit statuses are stable: 0 for success, 1 for execution failure, 2 for
invalid usage or configuration, and 130 when interrupted. See the
automation guide for scripting and CI examples.
Here we have 4 tables showing a customer and their orders on a ecommerce site:
| Table | Column | Type | Relationship |
|---|---|---|---|
customer |
id |
integer | Primary key |
name |
varchar(80) | ||
address |
id |
integer | Primary key |
customer_id |
integer | Foreign key → customer.id |
|
addr1 |
varchar(80) | ||
city |
varchar(30) | ||
primary_residence |
char | ||
customer_order |
id |
integer | Primary key |
customer_id |
integer | Foreign key → customer.id |
|
ordered_on |
date | ||
order_item |
id |
integer | Primary key |
order_id |
integer | Foreign key → customer_order.id |
|
sku |
varchar(40) | ||
quantity |
integer |
Customers
| ID | NAME |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Yuki |
Address
| ID | CUSTOMER_ID | ADDR1 | CITY | PRIMARY_RESIDENCE |
|---|---|---|---|---|
| 1 | 1 | 21 acacia drive | Tokyo | Y |
| 2 | 1 | 11 Downing Street | London | N |
| 3 | 2 | 1 George Street | Sydney | Y |
Customer Order
| ID | CUSTOMER_ID | ORDERED_ON |
|---|---|---|
| 1001 | 1 | 2026-07-01 |
| 1002 | 1 | 2026-07-15 |
| 1003 | 2 | 2026-07-20 |
Order Item
| ID | ORDER_ID | SKU | QUANTITY |
|---|---|---|---|
| 1 | 1001 | TEA | 2 |
| 2 | 1001 | CAKE | 1 |
| 3 | 1002 | MUG | 2 |
| 4 | 1003 | COFFEE | 1 |