Skip to content

Pull one borehole record set

Available in: Python · Node · DuckDB · Browser

Fan out from a LOCA location to everything that hangs off it — samples, tests, the lot — as typed frames keyed by group code. Reach for this when you want a single borehole's whole story, not one group at a time.

First, see which groups fan out for the boreholes you picked — .at(code, ids) returns a query whose .groups lists the related group set:

# what this shows: ags.at(code, ids) fans out to one borehole's related group set, exposed as q.groups.
import laterite

ags = laterite.read("examples/sample_site.ags")
q = ags.at("LOCA", ["BH01", "BH02"])

print(q.groups)
assert "LOCA" in q.groups and "SAMP" in q.groups
['LOCA', 'SAMP', 'LLPL']

.at("LOCA", ["BH01", "BH02"]) walks the dictionary's parent graph down from LOCA and keeps only the groups that actually carry rows for those locations. q.groups is the manifest — LOCA itself, SAMP (samples), LLPL (Atterberg limits) — so you know what's coming before you materialise anything.

Then call .frames() to materialise the record set as {group_code: frame}:

# what this shows: materialise a borehole's full record set as a dict of polars frames via .at(..).frames()
import laterite

ags = laterite.read("examples/sample_site.ags")
frames = ags.at("LOCA", ["BH01"]).frames()

# frames is a dict {group_code: polars frame}; pull one out by code (NOT q["SAMP"])
print(sorted(frames))
print(frames["SAMP"].height)

assert isinstance(frames, dict) and "SAMP" in frames and frames["SAMP"].height >= 1
['LLPL', 'LOCA', 'SAMP']
4

frames is a plain dict of born-typed polars frames — pull one out by its 4-letter code (frames["SAMP"], not q["SAMP"]). Each frame is already typed straight from the AGS TYPE row, and each is row-filtered to just the boreholes you asked for, so frames["SAMP"] here is the four samples taken on BH01.

// what this shows: at(code, ids) fans out to a borehole's related groups; frames() materialises the record set.
// Needs the optional @duckdb/node-api peer (npm i @duckdb/node-api) — read/validate/fix don't.
import { read } from "laterite";
import assert from "node:assert/strict";

const ags = read("examples/sample_site.ags");

// at("LOCA", ids) walks the dictionary's parent graph down from LOCA and keeps
// only groups that carry rows for those locations; `.groups` is the manifest.
const q = ags.at("LOCA", ["BH01"]);
console.log(q.groups.sort());

// frames() returns { group: rows } for the whole related record set, each
// row-filtered to just the boreholes you asked for.
const frames = await q.frames();
console.log(Object.keys(frames).sort());
console.log(frames.SAMP.length);
ags.close();

assert.ok(q.groups.includes("LOCA") && q.groups.includes("SAMP"));
assert.ok(Array.isArray(frames.SAMP) && frames.SAMP.length >= 1);
[ 'LLPL', 'LOCA', 'SAMP' ]
[ 'LLPL', 'LOCA', 'SAMP' ]
4

Same fan-out, same manifest: at("LOCA", ids) returns a subset whose .groups lists the related codes, and frames() materialises them as { group: rows } — plain JS row objects (or arrow-js Tables with { arrow: true }), each filtered to the boreholes you asked for. It's async and rides the same optional peer as sql() (npm i @duckdb/node-api); close() releases the connection when you're done.

-- One borehole's record set via keyed joins — LOCA to its SAMP rows on the shared key.
-- (DuckDB has no fan-out helper; you name the related groups and join them yourself.)
-- expect-rows: 4
SELECT l.loca_id, s.samp_ref, s.samp_top, s.samp_type
FROM read_ags('examples/sample_site.ags', 'LOCA') l
JOIN read_ags('examples/sample_site.ags', 'SAMP') s USING (loca_id)
WHERE l.loca_id = 'BH01'
ORDER BY s.samp_top;

DuckDB has no fan-out helper — you name the related groups and join them on the shared KEY yourself. Each group is a read_ags() table function and the columns are born-typed, so samp_top sorts numerically. To pull every related group at once, add a read_ags(...) and JOIN per code, or reach for load_ags(path) to emit the DDL that materialises them all as ags_<code> tables first.

Open the web app and load your file into the Explore pane. Pick a LOCA location and the related groups surface alongside it — the same parent-graph fan-out, point-and-click — so you can read one borehole's samples and tests together without writing a query. The file never leaves your machine.

When to use it: building a per-location report, exporting one hole's data, or feeding a downstream model that wants the whole record set at once. Gotcha: .at(...)/frames() is a fan-out, not a join — each group stays a separate frame keyed on its own KEY heading. If you want the groups joined into one wide result, run SQL across them instead (the DuckDB tab shows the join form).

See also: SQL across groups · Born-typed