Skip to main content
The Narrative SDK provides methods to execute, validate, and compile NQL queries programmatically. This guide covers common query patterns and best practices.
For NQL syntax and language features, see the NQL Reference.

Prerequisites

Running a query

Basic execution

Use executeNql() to run a query and retrieve results:
import { NarrativeApi } from '@narrative.io/data-collaboration-sdk-ts';

const api = new NarrativeApi({
  apiKey: process.env.NARRATIVE_API_KEY,
});

const result = await api.executeNql({
  nql: `
    SELECT _nio_id, _nio_updated_at
    FROM company_data."my_dataset"
    LIMIT 100
  `,
  data_plane_id: null,
});

console.log('Query state:', result.state);
console.log('Rows returned:', result.result.rows);
console.log('Cost:', result.result.cost);

Query input options

The executeNql() method accepts the following options:
OptionTypeRequiredDescription
nqlstringYesThe NQL query to execute
data_plane_idstring | nullYesTarget data plane ID, or null for default
execution_clusterobjectNoCluster configuration (shared or dedicated)
create_as_viewbooleanNoCreate the query as a view
const result = await api.executeNql({
  nql: 'SELECT * FROM company_data."my_dataset" LIMIT 10',
  data_plane_id: null,
  execution_cluster: { type: 'dedicated' },
});

Understanding the result

The NqlResult object contains execution metadata:
interface NqlResult {
  id: string;              // Job ID
  state: string;           // 'completed', 'failed', etc.
  created_at: string;      // When the query was submitted
  completed_at: string;    // When execution finished
  result: {
    type: string;
    rows: number;          // Number of rows returned
    cost: number;          // Query cost
    nql_type: string;
  };
  input: {
    nql: string;           // The original query
    compiled_select?: string;
  };
  failures: unknown[];     // Any execution errors
}

Validating a query

Use validateNql() to check query syntax without execution:
const validation = await api.validateNql({
  nql: `
    SELECT _nio_id
    FROM company_data."my_dataset"
    LIMIT 10
  `,
  data_plane_id: null,
});

console.log('Query is valid');
If the query has syntax errors, the method throws an error with details about the issue.

Compiling a query

Use compileNql() to see the transpiled SQL without executing:
const compiled = await api.compileNql({
  nql: `
    SELECT _nio_id, _nio_updated_at
    FROM company_data."my_dataset"
    WHERE _nio_updated_at > CURRENT_DATE - INTERVAL '7' DAY
    LIMIT 100
  `,
  data_plane_id: null,
});

console.log('Transpiled SQL:', compiled.sql);

if (compiled.mappingErrors) {
  console.log('Mapping errors:', compiled.mappingErrors);
}
This is useful for:
  • Debugging query issues
  • Understanding how NQL maps to the underlying SQL dialect
  • Verifying Rosetta Stone mappings

Parsing a query

Use parseNql() to get the abstract syntax tree (AST):
const ast = await api.parseNql({
  nql: 'SELECT _nio_id FROM company_data."my_dataset" LIMIT 10',
  data_plane_id: null,
});

console.log('AST:', JSON.stringify(ast, null, 2));

Retrieving a previous query

If you have a job ID from a previous query, retrieve its results:
const result = await api.getNqlByJobId('job-id-here');

console.log('Query state:', result.state);
console.log('Rows returned:', result.result.rows);

Query patterns

Filtering data

const result = await api.executeNql({
  nql: `
    SELECT sha256_hashed_email, event_type, event_timestamp
    FROM company_data."customer_events"
    WHERE event_timestamp >= CURRENT_DATE - INTERVAL '30' DAY
      AND event_type = 'purchase'
    LIMIT 1000
  `,
  data_plane_id: null,
});

Aggregations

const result = await api.executeNql({
  nql: `
    SELECT
      event_type,
      COUNT(*) as event_count
    FROM company_data."customer_events"
    GROUP BY event_type
    LIMIT 100
  `,
  data_plane_id: null,
});

Using Rosetta Stone attributes

Query normalized data across datasets:
const result = await api.executeNql({
  nql: `
    SELECT
      hl7_gender,
      unique_id,
      timestamp_utc
    FROM narrative.rosetta_stone
    LIMIT 100
  `,
  data_plane_id: null,
});

Error handling

Wrap query execution in try-catch to handle errors:
try {
  const result = await api.executeNql({
    nql: 'SELECT * FROM company_data."my_dataset" LIMIT 10',
    data_plane_id: null,
  });

  if (result.state === 'completed') {
    console.log('Success:', result.result.rows, 'rows');
  } else {
    console.log('Query state:', result.state);
  }
} catch (error) {
  if (error.status === 400) {
    console.error('Invalid query:', error.message);
  } else if (error.status === 403) {
    console.error('Permission denied');
  } else {
    console.error('Query failed:', error);
  }
}

Best practices

PracticeDescription
Always use LIMITPrevent unbounded result sets
Validate before executingUse compileNql() to catch errors early
Handle errors gracefullyWrap calls in try-catch
Use appropriate timeoutsLong-running queries may need monitoring
Filter at the sourceApply WHERE clauses to reduce data scanned