# RecordQueryResult

> Part of the NocoDB documentation (Workflows > Scripts > API Reference). Index of all pages: https://nocodb.com/llms.txt. Any docs page is available as Markdown by adding `.md` to its URL.

URL: https://nocodb.com/docs/workflows/scripts/api-reference/record-query-result
Last updated: 2026-10-03

The RecordQueryResult object in NocoDB Scripts holds the records from a query and supports pagination.

The `RecordQueryResult` object represents the results of a record query in NocoDB Scripts. It gives access to the records that the query returns. It supports pagination, so you can work with large datasets.

## Overview

When you query records from a table or view with `selectRecordsAsync()`, the method returns a `RecordQueryResult` object. Use this object to:

* Access the array of returned records
* Check if more records are available than the query returned first
* Load additional pages of records when needed
* Get record count and pagination information
* Access specific records by ID

The RecordQueryResult object helps you work efficiently with query results. This is important for large tables, where it is possibly not practical to load all records at once.

## Properties

| Property         | Type                          | Description                                                |
| ---------------- | ----------------------------- | ---------------------------------------------------------- |
| `records`        | `ReadonlyArray<NocoDBRecord>` | Array of NocoDBRecord objects returned by the query        |
| `recordIds`      | `ReadonlyArray<string>`       | Array of record IDs (strings) returned by the query        |
| `hasMoreRecords` | `boolean`                     | Whether more records are available than the query returned |
| `table`          | `Table`                       | Reference to the table that the records came from          |
| `raw_data`       | `Array`                       | The raw underlying data from the API response              |

## Methods

### getRecord

Gets a specific record from the query result by its ID.

**Parameters:**

* `recordId` (`string`): The ID of the record to retrieve

**Returns:** `NocoDBRecord` object corresponding to the specified ID

**Throws:** An error with the message "No record matching "\[recordId]" found in query". This occurs if the query result has no record with the specified ID

**Example:**

```javascript
// Get a table and query some records
const projectsTable = base.getTable('Projects');
const projectsResult = await projectsTable.selectRecordsAsync();

// Get a specific record from the results
try {
  const projectId = projectsResult.recordIds[0];
  const project = projectsResult.getRecord(projectId);
  
  output.text(`Project name: ${project.getCellValue('Name')}`);
} catch (error) {
  output.text(`Error retrieving record: ${error.message}`);
}
```

### loadMoreRecords

Loads the next page of records when hasMoreRecords is true. This method adds the new records to the end of the existing records array. It returns the same RecordQueryResult instance with the updated data.

**Returns:** `Promise<RecordQueryResult>`: A promise that resolves to the same RecordQueryResult instance, with the new records added to the end of the records array. It resolves to `null` if there are no more records to load

**Example:**

```javascript
// Get a table with many records
const tasksTable = base.getTable('Tasks');

// Initial query with a small page size
const tasksResult = await tasksTable.selectRecordsAsync({
  pageSize: 50 // Start with 50 records
});

while (tasksResult.hasMoreRecords) {
  await tasksResult.loadMoreRecords();
}

output.text(`Loaded ${tasksResult.records.length} tasks.`);
```

## Working with Query Results

### Basic Usage

```javascript
// Query records from a table
const projectsTable = base.getTable('Projects');
const queryResult = await projectsTable.selectRecordsAsync({
  fields: ['Name', 'Status', 'Due Date', 'Assigned To'],
  sorts: [
    { field: 'Due Date', direction: 'asc' }
  ],
  pageSize: 100
});

// Output basic information about the result
output.text(`Query returned ${queryResult.records.length} records.`);
output.text(`Has more records: ${queryResult.hasMoreRecords}`);

// Process the records
for (const record of queryResult.records) {
  const projectName = record.getCellValue('Name');
  const status = record.getCellValue('Status');
  const dueDate = record.getCellValue('Due Date');
  
  output.text(`- ${projectName} (${status}), due on ${dueDate}`);
}
```

### Paginated Processing

For tables with many records, use pagination to process the records in batches:

```javascript
// Process all records in a large table using pagination
const customersTable = base.getTable('Customers');

// Process all records in a large table using pagination
async function processAllRecords() {
  let totalProcessed = 0;
  let batchNumber = 1;
  
  // Start with initial query
  let queryResult = await customersTable.selectRecordsAsync({
    fields: ['Name', 'Email', 'Last Order Date', 'Total Spend'],
    pageSize: 100
  });
  
  // Process records in batches
  do {
    const currentBatchSize = queryResult.records.length - totalProcessed;
    const startIndex = totalProcessed;
    
    output.text(`Processing batch ${batchNumber} (${currentBatchSize} records)...`);
    
    // Process only the new records from this batch
    for (let i = startIndex; i < queryResult.records.length; i++) {
      const record = queryResult.records[i];
      const data = await processCustomerRecord(record);
      output.inspect({
        recordId: record.id,
        name: record.getCellValue('Name'),
        status: data
      });
      totalProcessed++;
    }
    
    output.text(`✓ Completed batch ${batchNumber}: ${currentBatchSize} records processed`);
    
    // Load next batch if available
    if (queryResult.hasMoreRecords) {
      await queryResult.loadMoreRecords();
      batchNumber++;
      
      // Optional: Add delay between batches to avoid rate limits
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
  } while (queryResult.hasMoreRecords);
  
  output.text(`Total records processed: ${totalProcessed}`);
  return totalProcessed;
}



// Example of a record processing function
async function processCustomerRecord(record) {
  // Simulate processing time
  await new Promise(resolve => setTimeout(resolve, 10));
  
  // Get the last order date
  const lastOrderDate = record.getCellValue('Last Order Date');
  
  // Check if it's been more than 90 days since the last order
  if (lastOrderDate) {
    const daysSinceOrder = Math.floor((new Date() - new Date(lastOrderDate)) / (1000 * 60 * 60 * 24));
    
    if (daysSinceOrder > 90) {
      return 'inactive';
    } else {
      return 'active';
    }
  } else {
    return 'never-ordered';
  }
}

// Run the process
await processAllRecords();
```

### Finding Records in the Result

```javascript
// Query records and then find specific ones
const employeesTable = base.getTable('Employees');
const employeesResult = await employeesTable.selectRecordsAsync({
  fields: ['Name', 'Department', 'Position', 'Hire Date', 'Salary']
});

// Find employees from a specific department
const hrEmployees = employeesResult.records.filter(
  record => record.getCellValue('Department') === 'Human Resources'
);

output.text(`Found ${hrEmployees.length} employees in HR department.`);

// Find the employee with the highest salary
let highestPaidEmployee = null;
let highestSalary = 0;

for (const record of employeesResult.records) {
  const salary = record.getCellValue('Salary') || 0;
  
  if (salary > highestSalary) {
    highestSalary = salary;
    highestPaidEmployee = record;
  }
}

if (highestPaidEmployee) {
  output.text('Highest paid employee:');
  output.text(`- Name: ${highestPaidEmployee.getCellValue('Name')}`);
  output.text(`- Department: ${highestPaidEmployee.getCellValue('Department')}`);
  output.text(`- Position: ${highestPaidEmployee.getCellValue('Position')}`);
  output.text(`- Salary: $${highestPaidEmployee.getCellValue('Salary').toLocaleString()}`);
}
```

### Grouping Records

```javascript
// Query and group records
const tasksTable = base.getTable('Tasks');
const tasksResult = await tasksTable.selectRecordsAsync({
  fields: ['Task Name', 'Status', 'Priority', 'Assigned To', 'Due Date']
});

// Group tasks by status
const tasksByStatus = {};

for (const record of tasksResult.records) {
  const status = record.getCellValue('Status') || 'No Status';
  
  if (!tasksByStatus[status]) {
    tasksByStatus[status] = [];
  }
  
  tasksByStatus[status].push({
    id: record.id,
    name: record.getCellValue('Task Name'),
    priority: record.getCellValue('Priority'),
    assignee: record.getCellValue('Assigned To')?.name || 'Unassigned',
    dueDate: record.getCellValue('Due Date')
  });
}

// Display grouped tasks
output.markdown('# Tasks by Status');

for (const [status, tasks] of Object.entries(tasksByStatus)) {
  output.markdown(`## ${status} (${tasks.length})`);
  
  // Sort tasks by priority (if available)
  tasks.sort((a, b) => (b.priority || 0) - (a.priority || 0));
  
  // Display tasks in this group
  output.table(tasks);
}
```

## Analytics on Query Results

```javascript
// Query sales data and perform analytics
const salesTable = base.getTable('Sales');
const salesResult = await salesTable.selectRecordsAsync({
  fields: ['Date', 'Product', 'Quantity', 'Unit Price', 'Total', 'Sales Rep', 'Region']
});

while(salesResult.hasMoreRecords) {
  await salesResult.loadMoreRecords()
}

// Calculate total sales
let totalSales = 0;
const salesByProduct = {};
const salesByRep = {};
const salesByRegion = {};
const salesByMonth = {};

for (const record of salesResult.records) {
  const total = record.getCellValue('Total') || 0;
  const product = record.getCellValue('Product') || 'Unknown';
  const salesRep = record.getCellValue('Sales Rep')?.name || 'Unknown';
  const region = record.getCellValue('Region') || 'Unknown';
  const date = record.getCellValue('Date');
  
  // Add to total sales
  totalSales += total;
  
  // Group by product
  if (!salesByProduct[product]) {
    salesByProduct[product] = 0;
  }
  salesByProduct[product] += total;
  
  // Group by sales rep
  if (!salesByRep[salesRep]) {
    salesByRep[salesRep] = 0;
  }
  salesByRep[salesRep] += total;
  
  // Group by region
  if (!salesByRegion[region]) {
    salesByRegion[region] = 0;
  }
  salesByRegion[region] += total;
  
  // Group by month
  if (date) {
    const month = new Date(date).toLocaleString('default', { month: 'long', year: 'numeric' });
    if (!salesByMonth[month]) {
      salesByMonth[month] = 0;
    }
    salesByMonth[month] += total;
  }
}

// Prepare data for output
output.markdown('# Sales Analysis');
output.text(`Total Sales: $${totalSales.toLocaleString()}`);
output.text(`Number of Transactions: ${salesResult.records.length}`);

// Sales by product
output.markdown('## Sales by Product');
const productData = Object.entries(salesByProduct)
  .map(([product, total]) => ({ Product: product, 'Total Sales': total }))
  .sort((a, b) => b['Total Sales'] - a['Total Sales']);
output.table(productData);

// Sales by rep
output.markdown('## Sales by Representative');
const repData = Object.entries(salesByRep)
  .map(([rep, total]) => ({ 'Sales Rep': rep, 'Total Sales': total }))
  .sort((a, b) => b['Total Sales'] - a['Total Sales']);
output.table(repData);

// Sales by region
output.markdown('## Sales by Region');
const regionData = Object.entries(salesByRegion)
  .map(([region, total]) => ({ Region: region, 'Total Sales': total }))
  .sort((a, b) => b['Total Sales'] - a['Total Sales']);
output.table(regionData);

// Sales by month
output.markdown('## Sales by Month');
const monthData = Object.entries(salesByMonth)
  .map(([month, total]) => ({ Month: month, 'Total Sales': total }))
  .sort((a, b) => {
    // Sort chronologically
    const dateA = new Date(a.Month);
    const dateB = new Date(b.Month);
    return dateA - dateB;
  });
output.table(monthData);
```

## Best Practices

1. **Use appropriate page sizes**: Choose a pageSize that balances performance and memory usage. The default is 50 records. Use a larger or smaller page size if your script needs it.

2. **Check for more records**: Always check the `hasMoreRecords` property. It tells you if you must load more pages of records.

3. **Process records in batches**: For large tables, process records in batches. Do not load all records at once.

4. **Track record indices when paginating**: `loadMoreRecords()` adds records to the end of the existing records array. Record which records you already processed, so you do not process them again.

5. **Handle linked records carefully**: Linked records can need more queries to get their full details.

6. **Use getRecord() for lookups**: To find a specific record by ID, use the `getRecord()` method. Do not search through the records array.

7. **Await async operations**: Always use `await` when you call async methods. This makes sure that the code runs in the correct order.

## Related

* [Base](/docs/workflows/scripts/api-reference/base)
* [Table](/docs/workflows/scripts/api-reference/table)
* [Record](/docs/workflows/scripts/api-reference/record)
* [View](/docs/workflows/scripts/api-reference/view)

---

## Related pages

- [Base](https://nocodb.com/docs/workflows/scripts/api-reference/base.md): The base object in NocoDB Scripts gives access to the tables and collaborators of the current base.
- [Cursor](https://nocodb.com/docs/workflows/scripts/api-reference/cursor.md): The cursor object in NocoDB Scripts tells a script which base, table, view and row are active.
- [Table](https://nocodb.com/docs/workflows/scripts/api-reference/table.md): The Table object in NocoDB Scripts represents a table and gives methods to query, create, update, and delete its records.
- [View](https://nocodb.com/docs/workflows/scripts/api-reference/view.md): The View object in NocoDB Scripts represents a view of a table and lets you query its records.
- [Field](https://nocodb.com/docs/workflows/scripts/api-reference/field.md): The Field object in NocoDB Scripts gives access to the configuration of a table column, and the field types and their cell values.
- [Record](https://nocodb.com/docs/workflows/scripts/api-reference/record.md): The NocoDBRecord object in NocoDB Scripts represents one table row and gives methods to read its cell values.
- [Session](https://nocodb.com/docs/workflows/scripts/api-reference/session.md): The session object in NocoDB Scripts gives information about the user who runs the script.
- [Collaborator](https://nocodb.com/docs/workflows/scripts/api-reference/collaborator.md): The Collaborator object in NocoDB Scripts holds the ID, name and email of a user who has access to a base.
