Exploring op.select()
Project and shape columns before a plan gets harder to read than the data
op.select() is where Optic plans become easier to read and easier to hand off. Instead of passing every available column downstream, choose the ones that matter and give the output a stable shape that consumers can rely on.
Use it when plans are growing beyond a simple one-view read and column clarity matters for whoever maintains them next.
Llamaverse Context for This API Call
| Context Item | What this article uses |
|---|---|
| Data set | llamaverse v2.0+ sample data |
| Primary source documents | /cleverllamas/llamaverse/raw/wild-llamas/llamas/{uuid}.json |
| Schema and views | llamaverse.llamas and llamaverse.secretPowers |
| Join relationship shown | llamas.secretPowerId -> secretPowers.id |
| Fields used | None in these examples |
| Index context | Rows come from TDE view extraction; join behaviour depends on view columns being available in the same database |
If joins return fewer rows than expected, validate that both views are present and that the join columns contain compatible values.
Source Docs and Configuration Used
| Item | Value used in this article |
|---|---|
| MarkLogic API docs | op:select, op.select |
| Companion functions used | op:join-left-outer, op:view-col, op:as |
| Llamaverse source collection | wild-llamas |
| Llamaverse source URI pattern | /cleverllamas/llamaverse/raw/wild-llamas/llamas/{uuid}.json |
| Required database config | TDE templates must expose views llamaverse.llamas and llamaverse.secretPowers with join keys secretPowerId and id |
When to Use op.select()
Use it when a joined plan is producing columns that are not part of the answer, when column names from two views collide, or when downstream consumers need a clean and predictable output shape.
Leave it out when you are doing a quick diagnostic read from a single view. Add it back the moment another developer asks "which column is which".
Parameters
| Name | Datatype | Required | Notes |
|---|---|---|---|
plan | Object | Yes | The input plan or set of plans from which to select columns. |
columns | Array of strings, op.col(), or op.viewCol() references | Yes | An array of column names or expressions to select. |
qualifier | String | No | An optional qualifier for the selected columns, useful for disambiguating column names in joins or complex queries. |
Projection Flow (at a glance)
Simple Example
Building on the op.fromView() sample, the output shape here is clear but noisy — schema, view, and column names all appear in the result keys. That is fine for a single-view read.
{
"name": "Aaron",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
op:from-view("llamaverse", "llamas")
=> op:select((
op:view-col("llamas", "name"),
op:view-col("llamas", "placeOfBirth")
))
=> op:order-by("name")
=> op:limit(3)
=> op:result()
'use strict';
const op = require('/MarkLogic/optic');
const llamaPlan = op.fromView('llamaverse', 'llamas')
.select([op.viewCol('llamas', 'name'), op.viewCol('llamas', 'placeOfBirth')])
.orderBy('name')
.limit(3);
const results = llamaPlan.result();
({
sample: 'optic/op-select/assets/simple-select.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
Handling Two Views
This sample sources from two views — llamas and secretPowers — with a join. Joins are covered separately; this example focuses on what op.select() does once columns from two views are in the plan.
Two views means collisions are almost guaranteed: both views have a column named id. Exact view scoping is required to avoid ambiguity. The accessor in results is row["llamaverse.llamas.id"] because the column name is a literal string — row.llamaverse.llamas.id will not work.
{
"name": "Aaron",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
let $llamas := op:from-view("llamaverse", "llamas")
let $secretPowers := op:from-view("llamaverse", "secretPowers")
return
$llamas
=> op:join-left-outer(
$secretPowers,
op:on(
op:view-col("llamas", "secretPowerId"),
op:view-col("secretPowers", "id")
)
)
=> op:select((
op:view-col("llamas", "id"),
op:view-col("llamas", "name"),
op:view-col("secretPowers", "id"),
op:view-col("secretPowers", "name"),
op:view-col("secretPowers", "description")
))
=> op:limit(3)
=> op:result()
'use strict';
const op = require('/MarkLogic/optic');
const llamaPlan = op.fromView('llamaverse', 'llamas');
const secretPowersPlan = op.fromView('llamaverse', 'secretPowers');
const results = llamaPlan
.joinLeftOuter(secretPowersPlan, op.on(op.viewCol('llamas', 'secretPowerId'), op.viewCol('secretPowers', 'id')))
.select([
op.viewCol('llamas', 'id'),
op.viewCol('llamas', 'name'),
op.viewCol('secretPowers', 'id'),
op.viewCol('secretPowers', 'name'),
op.viewCol('secretPowers', 'description')
])
.limit(3)
.result();
({
sample: 'optic/op-select/assets/two-views.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
Normalising Column Names with op.select()
Use the qualifier parameter in op.select() to normalise column naming across views. Column-name ambiguity has to be resolved first — here, op.as() renames one side before the qualifier is applied.
For secretPowerId, the reference must be explicit because the llamas view has a similarly named column in the plan even if it is not selected. That is precisely why explicit column references are safer in larger plans. The qualifier result normalises all selected columns under a single clean prefix.
{
"name": "Aaron",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
let $llamas := op:from-view("llamaverse", "llamas")
let $secretPowers := op:from-view("llamaverse", "secretPowers")
return
$llamas
=> op:join-left-outer(
$secretPowers,
op:on(
op:view-col("llamas", "secretPowerId"),
op:view-col("secretPowers", "id")
)
)
=> op:select((
op:view-col("llamas", "id"),
op:as("llamaName", op:view-col("llamas", "name")),
op:as(op:view-col("secretPowers", "secretPowerId"), op:view-col("secretPowers", "id")),
op:as("secretPowerName", op:view-col("secretPowers", "name")),
op:as("secretPowerdescription", op:view-col("secretPowers", "description"))
), "result")
=> op:limit(3)
=> op:result()
'use strict';
const op = require('/MarkLogic/optic');
const llamaPlan = op.fromView('llamaverse', 'llamas');
const secretPowersPlan = op.fromView('llamaverse', 'secretPowers');
const results = llamaPlan
.joinLeftOuter(secretPowersPlan, op.on(op.viewCol('llamas', 'secretPowerId'), op.viewCol('secretPowers', 'id')))
.select([
op.viewCol('llamas', 'id'),
op.as('llamaName', op.viewCol('llamas', 'name')),
op.as(op.viewCol('secretPowers', 'secretPowerId'), op.viewCol('secretPowers', 'id')),
op.as('secretPowerName', op.viewCol('secretPowers', 'name')),
op.as('secretPowerdescription', op.viewCol('secretPowers', 'description'))
], 'result')
.limit(3)
.result();
({
sample: 'optic/op-select/assets/normalize-qualifier.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
Removing Schema and View Name Prefixes
Pass an empty string as the qualifier to strip schema and view prefixes entirely. Results come back with bare column names.
{
"name": "Aaron",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
let $llamas := op:from-view("llamaverse", "llamas")
let $secretPowers := op:from-view("llamaverse", "secretPowers")
return
$llamas
=> op:join-left-outer(
$secretPowers,
op:on(
op:view-col("llamas", "secretPowerId"),
op:view-col("secretPowers", "id")
)
)
=> op:select((
op:view-col("llamas", "id"),
op:as("llamaName", op:view-col("llamas", "name")),
op:as(op:view-col("secretPowers", "secretPowerId"), op:view-col("secretPowers", "id")),
op:as("secretPowerName", op:view-col("secretPowers", "name")),
op:as("secretPowerdescription", op:view-col("secretPowers", "description"))
), "")
=> op:limit(3)
=> op:result()
'use strict';
const op = require('/MarkLogic/optic');
const llamaPlan = op.fromView('llamaverse', 'llamas');
const secretPowersPlan = op.fromView('llamaverse', 'secretPowers');
const results = llamaPlan
.joinLeftOuter(secretPowersPlan, op.on(op.viewCol('llamas', 'secretPowerId'), op.viewCol('secretPowers', 'id')))
.select([
op.viewCol('llamas', 'id'),
op.as('llamaName', op.viewCol('llamas', 'name')),
op.as(op.viewCol('secretPowers', 'secretPowerId'), op.viewCol('secretPowers', 'id')),
op.as('secretPowerName', op.viewCol('secretPowers', 'name')),
op.as('secretPowerdescription', op.viewCol('secretPowers', 'description'))
], '')
.limit(3)
.result();
({
sample: 'optic/op-select/assets/remove-schema-view-prefixes.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
When to Choose op.select()
Use op.select() when a plan is growing beyond a simple view read and column clarity matters for whoever maintains it next.
Use op.as() when renaming matters. Pass an empty-string qualifier to strip schema and view prefixes entirely. Start explicit — the maintenance savings compound faster than you expect.
- Exploring op.fromView() — Source the rows you'll select and shape here.
- Optic Joins Starter Pack —
op.select()keeps joined plans readable; understand join shapes before adding projection. - Optic Data Manipulation Starter Pack — Continue to transforms and expressions once column shapes are stable.
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- Llamaverse Context for This API Call
- Source Docs and Configuration Used
- When to Use op.select()
- Parameters
- Projection Flow (at a glance)
- Simple Example
- Handling Two Views
- Normalising Column Names with op.select()
- Removing Schema and View Name Prefixes
- When to Choose op.select()