OData Query Builder

Query Options

Type-safe OData query construction

from easy_acumatica.odata import QueryOptions, F

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

Overview

QueryOptions builds OData queries for the Acumatica REST API. Handles URL encoding and parameter formatting.

Quick Start Examples

Python
from easy_acumatica.odata 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.odata 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)
    )
)