← Files NetSuite SuiteCloudARCHIVED FILE

skills/netsuite-sdf-safe-guide/references/appendices/appendix-nquery-joins.md

6.39 KB · Oct 2, 2026 · 00:30 UTC

↓ Download file

# Appendix: Scripting with Multi-Level Joins using N/query
> Source: Oracle NetSuite SAFE Guide (SuiteApp Architectural Fundamentals & Examples) Version 2025.2
> Author: Oracle NetSuite

## Overview

The N/query module overcomes the two-level record join limitation of N/search by allowing multi-level joins using the SuiteAnalytics Workbook query engine.

## N/query Capabilities

- **Multi-level joins** - Join field data from multiple record types
- **Advanced conditions** - AND, OR, NOT logic with formulas and relative dates
- **Flexible sorting** - Sort results based on multiple columns

## Key Objects

| Object | Purpose |
|--------|---------|
| Query | Initial query definition |
| Component | Building block for joins |
| Condition | Filters/conditions for results |
| Column | Fields to return in results |
| Sort | Sorting configuration |
| ResultSet | Query execution results |

## Complete Code Example

```javascript
/**
 * Multi-level join query example
 * Customer → Sales Rep (Employee) → Location
 * @NApiVersion 2.1
 */
require(['N/query', 'N/log'], function(query, log) {

    // Step 1: Create a query definition for Customer records.
    const myCustomerQuery = query.create({
        type: query.Type.CUSTOMER
    });

    // Step 2: Join based on salesrep field (references Employee record).
    const mySalesRepJoin = myCustomerQuery.autoJoin({
        fieldId: 'salesrep'
    });

    // Step 3: Join Employee to Location (third level).
    const myLocationJoin = mySalesRepJoin.autoJoin({
        fieldId: 'location'
    });

    // Step 4: Create conditions.
    // Condition 1: id = 1022
    const firstCondition = myCustomerQuery.createCondition({
        fieldId: 'id',
        operator: query.Operator.EQUAL,
        values: 1022
    });

    // Condition 2: id = 955
    const secondCondition = myCustomerQuery.createCondition({
        fieldId: 'id',
        operator: query.Operator.EQUAL,
        values: 955
    });

    // Condition 3: Email does not start with 'foo'.
    const thirdCondition = mySalesRepJoin.createCondition({
        fieldId: 'email',
        operator: query.Operator.START_WITH_NOT,
        values: 'foo'
    });

    // Step 5: Combine conditions with AND/OR.
    // (Email NOT STARTS WITH 'foo') AND (id = 1022 OR id = 955)
    myCustomerQuery.condition = myCustomerQuery.and(
        thirdCondition,
        myCustomerQuery.or(firstCondition, secondCondition)
    );

    // Step 6: Create columns from different record types.
    myCustomerQuery.columns = [
        myCustomerQuery.createColumn({
            fieldId: 'entityid'
        }),
        myCustomerQuery.createColumn({
            fieldId: 'id'
        }),
        mySalesRepJoin.createColumn({
            fieldId: 'entityid'
        }),
        mySalesRepJoin.createColumn({
            fieldId: 'email'
        }),
        mySalesRepJoin.createColumn({
            fieldId: 'hiredate'
        }),
        myLocationJoin.createColumn({
            fieldId: 'name'
        })
    ];

    // Step 7: Sort by multiple columns.
    myCustomerQuery.sort = [
        myCustomerQuery.createSort({
            column: myCustomerQuery.columns[3]  // email
        }),
        myCustomerQuery.createSort({
            column: myCustomerQuery.columns[0], // entityid
            ascending: false
        })
    ];

    // Step 8: Run the query.
    const resultSet = myCustomerQuery.run();

    // Step 9: Process results
    const results = resultSet.results;
    for (let i = results.length - 1; i >= 0; i--) {
        log.debug(results[i].values);
    }
    log.debug(resultSet.types);
});
```

## Building Queries Step by Step

### 1. Create Initial Query

```javascript
const myQuery = query.create({
    type: query.Type.CUSTOMER  // Root record type.
});
```

### 2. Create Joins

```javascript
// First level join
const firstJoin = myQuery.autoJoin({
    fieldId: 'salesrep'  // Field that references another record.
});

// Second level join (from first join)
const secondJoin = firstJoin.autoJoin({
    fieldId: 'location'
});
```

### 3. Create Conditions

```javascript
// Simple condition on root query
const condition1 = myQuery.createCondition({
    fieldId: 'id',
    operator: query.Operator.EQUAL,
    values: 123
});

// Condition on joined record
const condition2 = firstJoin.createCondition({
    fieldId: 'email',
    operator: query.Operator.CONTAIN,
    values: '@company.com'
});
```

### 4. Combine Conditions

```javascript
// AND conditions
myQuery.condition = myQuery.and(condition1, condition2);

// OR conditions
myQuery.condition = myQuery.or(condition1, condition2);

// Nested conditions
myQuery.condition = myQuery.and(
    condition1,
    myQuery.or(condition2, condition3)
);
```

### 5. Create Columns

```javascript
myQuery.columns = [
    // Column from root query
    myQuery.createColumn({ fieldId: 'entityid' }),

    // Column from first join
    firstJoin.createColumn({ fieldId: 'email' }),

    // Column from second join
    secondJoin.createColumn({ fieldId: 'name' })
];
```

### 6. Create Sort

```javascript
myQuery.sort = [
    myQuery.createSort({
        column: myQuery.columns[0],
        ascending: true,
        caseSensitive: false
    })
];
```

### 7. Run Query

```javascript
// Standard run
const resultSet = myQuery.run();
const results = resultSet.results;

// Paged run for large results
const pagedResults = myQuery.runPaged({ pageSize: 1000 });
pagedResults.pageRanges.forEach(function(pageRange) {
    const page = pagedResults.fetch({ index: pageRange.index });
    page.data.forEach(function(result) {
        // Process result
    });
});
```

## Common Query Operators

| Operator | Description |
|----------|-------------|
| EQUAL | Equals |
| NOT_EQUAL | Not equals |
| GREATER | Greater than |
| GREATER_OR_EQUAL | Greater than or equal |
| LESS | Less than |
| LESS_OR_EQUAL | Less than or equal |
| CONTAIN | Contains string |
| START_WITH | Starts with string |
| START_WITH_NOT | Does not start with |
| IS_EMPTY | Is null/empty |
| IS_NOT_EMPTY | Is not null/empty |
| ANY_OF | In list of values |
| NONE_OF | Not in list of values |

## SuiteAnalytics Workbook Equivalent

The SuiteScript query above produces the same results as:

1. Create new Workbook with Customer root record.
2. Add Sales Rep (Employee) as joined record.
3. Add Location from Sales Rep as second join.
4. Add filter: Sales Rep Email does not start with "foo".
5. Add filters: Internal ID = 955 OR Internal ID = 1022.
6. Select columns from all three record types.

SHA-256: 59673711f6e455c5a8d71080c93a5cd7f6f0f5be29e6ed59da664e53c3232498