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
.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
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);
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