OData Query Builder

Query Options

Build powerful OData queries with a clean, type-safe API

from easy_acumatica import QueryOptions, F

options = QueryOptions(
    filter=F.Amount > 1000,
    select=["CustomerID", "Amount", "Status"],
    expand=["Details"],
    order_by="Amount desc",
    top=50
)

Overview

The QueryOptions class provides a clean, type-safe way to build OData queries for the Acumatica REST API. It handles all the complex URL encoding and parameter formatting, letting you focus on your business logic.

Quick Start Examples

python
from easy_acumatica import QueryOptions

# Get top 10 customers
options = QueryOptions(top=10)
customers = client.customers.get_list(options)

# Get specific fields only
options = QueryOptions(
    select=["CustomerID", "CustomerName", "Email"]
)
customers = client.customers.get_list(options)

Parameters Reference

All QueryOptions parameters with examples:

Advanced Usage Patterns

Dynamic Query Building
Build queries dynamically based on runtime conditions
python
def build_customer_query(
    status=None,
    min_balance=None,
    customer_class=None,
    include_contacts=False,
    page=1,
    page_size=20
):
    """Build a dynamic customer query based on parameters."""
    
    # Start with base options
    options_dict = {
        "select": ["CustomerID", "CustomerName", "Balance", "Status"],
        "order_by": "CustomerName",
        "top": page_size,
        "skip": (page - 1) * page_size
    }
    
    # Build filter conditions
    conditions = []
    
    if status:
        conditions.append(F.Status == status)
    
    if min_balance is not None:
        conditions.append(F.Balance >= min_balance)
    
    if customer_class:
        conditions.append(F.CustomerClass == customer_class)
    
    # Combine conditions with AND
    if conditions:
        filter_expr = conditions[0]
        for condition in conditions[1:]:
            filter_expr = filter_expr & condition
        options_dict["filter"] = filter_expr
    
    # Add expansion if requested
    if include_contacts:
        options_dict["expand"] = ["MainContact", "Contacts"]
        options_dict["select"].extend(["MainContact.Email", "MainContact.Phone"])
    
    return QueryOptions(**options_dict)

# Usage examples
query1 = build_customer_query(status="Active", min_balance=5000)
query2 = build_customer_query(customer_class="RETAIL", include_contacts=True, page=2)
query3 = build_customer_query()  # No filters, just pagination
Efficient Pagination
Implement efficient pagination for large datasets
python
class PaginatedQuery:
    """Helper class for paginated queries."""
    
    def __init__(self, client, service_name, page_size=50):
        self.client = client
        self.service = getattr(client, service_name)
        self.page_size = page_size
    
    def get_all(self, base_options=None):
        """Get all records using pagination."""
        all_records = []
        skip = 0
        
        while True:
            # Build options for this page
            options_dict = {
                "top": self.page_size,
                "skip": skip,
                "order_by": "id"  # Consistent ordering required
            }
            
            # Merge with base options
            if base_options:
                if hasattr(base_options, 'to_dict'):
                    base_dict = base_options.to_dict()
                else:
                    base_dict = base_options
                options_dict.update(base_dict)
            
            options = QueryOptions(**options_dict)
            
            # Get page
            page = self.service.get_list(options)
            
            if not page:
                break
                
            all_records.extend(page)
            
            # Check if we got a full page
            if len(page) < self.page_size:
                break
            
            skip += self.page_size
            
        return all_records
    
    def get_page(self, page_number, base_options=None):
        """Get a specific page of results."""
        options_dict = {
            "top": self.page_size,
            "skip": (page_number - 1) * self.page_size,
            "order_by": "id"
        }
        
        if base_options:
            if hasattr(base_options, 'to_dict'):
                base_dict = base_options.to_dict()
            else:
                base_dict = base_options
            options_dict.update(base_dict)
        
        options = QueryOptions(**options_dict)
        return self.service.get_list(options)

# Usage
paginator = PaginatedQuery(client, "customers", page_size=100)

# Get all active customers
base_options = QueryOptions(filter=F.Status == "Active")
all_active = paginator.get_all(base_options)

# Get specific page
page_3 = paginator.get_page(3, base_options)
Performance Optimization
Optimize queries for maximum performance
python
# BAD: Retrieving all fields when you only need a few
bad_options = QueryOptions(
    filter=F.Status == "Active"
)
# This returns ALL fields for each record

# GOOD: Select only needed fields
good_options = QueryOptions(
    filter=F.Status == "Active",
    select=["CustomerID", "CustomerName", "Email", "Balance"]
)
# Much smaller payload, faster response

# GOOD: Expand only what you need
good_options = QueryOptions(
    expand=["MainContact"],
    select=[
        "CustomerID", 
        "CustomerName", 
        "MainContact.Email",
        "MainContact.Phone"
    ]
)

# BAD: Client-side filtering
all_customers = client.customers.get_list()
active = [c for c in all_customers if c.Status == "Active"]

# GOOD: Server-side filtering
options = QueryOptions(filter=F.Status == "Active")
active = client.customers.get_list(options)

# Monitoring query performance
import time

def measure_query(options):
    start = time.time()
    results = client.customers.get_list(options)
    duration = time.time() - start
    
    print(f"Query took {duration:.2f} seconds")
    print(f"Retrieved {len(results)} records")
    
    if hasattr(results, '__sizeof__'):
        size = sys.getsizeof(results) / 1024
        print(f"Payload size: ~{size:.1f} KB")
    
    return results

# Test different approaches
measure_query(QueryOptions(top=10))  # Baseline
measure_query(QueryOptions(top=10, select=["CustomerID"]))  # Minimal
Copying and Modifying Queries
Create variations of existing queries
python
# Base query
base_options = QueryOptions(
    select=["CustomerID", "CustomerName", "Balance"],
    order_by="CustomerName"
)

# Method 1: Using copy() method
active_options = base_options.copy(
    filter=F.Status == "Active"
)

# Method 2: Convert to dict and modify
base_dict = base_options.to_dict()
base_dict["filter"] = F.Status == "Inactive"
inactive_options = QueryOptions(**base_dict)

# Method 3: Build query templates
class QueryTemplates:
    @staticmethod
    def customer_summary(status=None, min_balance=None):
        """Standard customer summary query."""
        options = {
            "select": [
                "CustomerID",
                "CustomerName", 
                "Balance",
                "CreditLimit",
                "Status"
            ],
            "order_by": "Balance desc"
        }
        
        filters = []
        if status:
            filters.append(F.Status == status)
        if min_balance is not None:
            filters.append(F.Balance >= min_balance)
            
        if filters:
            filter_expr = filters[0]
            for f in filters[1:]:
                filter_expr = filter_expr & f
            options["filter"] = filter_expr
            
        return QueryOptions(**options)
    
    @staticmethod
    def customer_details(customer_id):
        """Detailed customer query."""
        return QueryOptions(
            filter=F.CustomerID == customer_id,
            expand=[
                "MainContact",
                "DefaultLocation",
                "CreditTerms",
                "Contacts",
                "Locations"
            ]
        )

# Usage
summary = QueryTemplates.customer_summary(status="Active", min_balance=1000)
details = QueryTemplates.customer_details("CUST001")

QueryOptions Methods

Available methods on QueryOptions instances:

MethodDescriptionReturnsExample
to_dict()Convert QueryOptions to a dictionaryDict[str, Any]options.to_dict()
to_params()Convert to URL parameters with $ prefixesDict[str, str]options.to_params()
copy(**kwargs)Create a copy with updated parametersQueryOptionsoptions.copy(top=50)

Filter Functions Reference

Need help with filter functions?

The filter parameter accepts Filter objects created with the F factory, which provides a comprehensive set of functions for building OData queries.

View Complete Filter Reference

Quick Examples

python
from easy_acumatica import QueryOptions, F

# Basic comparisons
options = QueryOptions(filter=F.Status == "Active")
options = QueryOptions(filter=(F.Amount > 1000) & (F.Amount <= 5000))

# String functions
options = QueryOptions(filter=F.CustomerName.startswith("ABC"))
options = QueryOptions(filter=F.Email.contains("@company.com"))

# Date functions
options = QueryOptions(filter=F.CreatedDate.year() == 2024)
options = QueryOptions(filter=(F.Date >= "2024-01-01") & (F.Date < "2024-07-01"))

# Complex filters
options = QueryOptions(
    filter=(
        (F.Status == "Active") & 
        (F.CustomerClass.in_(["WHOLESALE", "RETAIL"])) &
        (F.Balance > 0)
    )
)