Disjoining Intervals#
This section covers patterns for splitting intervals at breakpoints using
GIQL’s DISJOIN operator – partitioning a set into non-overlapping
segments and re-tiling features against a reference grid.
Partition a Set of Intervals#
Build a Disjoint Partition#
Split a set of intervals into the maximal set of non-overlapping sub-intervals defined by its own breakpoints:
SELECT DISTINCT disjoin_chrom, disjoin_start, disjoin_end
FROM DISJOIN(features)
ORDER BY disjoin_chrom, disjoin_start
Use case: Given ChIP-seq peak calls pooled from several samples, produce a
non-overlapping segment track – the equivalent of Bioconductor’s disjoin()
– so downstream signal or count aggregates never double-count a base covered
by two overlapping sample peaks.
Track Each Segment’s Parent#
Keep the parent feature alongside each sub-interval:
SELECT name, disjoin_start, disjoin_end
FROM DISJOIN(features)
ORDER BY name, disjoin_start
Use case: See how each original feature was fragmented and which segment came from which parent.
Split Against a Reference#
Split Features Against a Mask#
Cut target features at the boundaries of a reference (mask) set, keeping only the pieces the mask covers:
SELECT name, disjoin_start, disjoin_end
FROM DISJOIN(features, reference := mask)
Use case: Restrict ATAC-seq or gene features to a set of callable (or otherwise interesting) mask regions, splitting them at the mask boundaries so no reported piece straddles a mask edge.
Re-tile Against a Uniform Grid#
Pass a generated set of fixed-width bins as the reference:
WITH bins AS (
SELECT 'chr1' AS chrom, x AS start, x + 1000 AS "end"
FROM range(0, 250000000, 1000) AS t(x)
)
SELECT * FROM DISJOIN(features, reference := bins)
Use case: Break features onto a uniform coordinate grid – for example to build a fixed-width binned coverage matrix – so each piece falls within a single bin.
Note
range() is DuckDB-specific syntax for generating the bin grid; other
engines need their own generator. The grid must also span every chromosome
present in features, or features on an uncovered chromosome are dropped.
The CTE / Subquery Reference Contract#
When the reference operand is an in-query CTE or a (SELECT ...)
subquery (rather than a registered table), it must project the canonical
chrom / start / end columns – the 0-based, half-open genomic
coordinates the operator references. A registered table may declare a custom
column mapping or coordinate system, but a CTE or subquery carries no such
declaration, so GIQL assumes it is already canonical.
GIQL validates this contract at transpile time. If a CTE or subquery operand
omits one of the canonical columns, transpile() raises a ValueError
naming the operator, the offending relation, and the missing column(s) – a
clear GIQL-level diagnostic rather than an opaque engine column not found
error at execution time. For example, reference := (SELECT chrom, start FROM
mask) is rejected because it omits end.
-- Valid: the subquery projects all three canonical columns
SELECT * FROM DISJOIN(features, reference := (
SELECT chrom, start, "end" FROM mask WHERE score > 10
))
This check is best-effort. When the operand’s output columns cannot be
determined statically – a SELECT * star projection, or a placeholder such
as SELECT 1 – the operand is passed through without a diagnostic (the
relation’s schema is unknown at transpile time), and a genuinely missing column
would still surface as an engine error at execution time.
Coming from Bedtools?#
DISJOIN has no direct bedtools equivalent – no single bedtools command
splits intervals at breakpoints. See the Bedtools Migration Guide guide for
the GIQL operators that do map onto bedtools commands.