Skip to content

Query Optimization

Optimize your queries to the Danish Parliamentary OData API for better performance, reduced bandwidth usage, and faster response times. This guide provides proven techniques based on extensive API testing.

Overview

The API performs exceptionally well with response times ranging from 85ms for small queries to 2.1 seconds for large datasets (10K+ records). However, optimization can dramatically improve performance and reduce costs.

Quick Wins

  • Use $select to fetch only needed fields (reduces response size by 70-90%)
  • Apply $filter before $expand to process fewer records
  • Limit $expand to essential relationships only
  • Monitor result counts to catch silent filter failures

Field Selection Optimization

Use $select to Reduce Response Size

The most effective optimization is selecting only the fields you need. This can reduce response size by 70-90% and improve parsing speed.

Field Selection Examples

L Unoptimized: Full Records

curl "https://oda.ft.dk/api/Sag?%24top=100"
# Response: ~150KB, includes all 20+ fields per record

 Optimized: Selected Fields Only

curl "https://oda.ft.dk/api/Sag?%24select=id,titel,opdateringsdato&%24top=100"
# Response: ~25KB, 83% size reduction

Select Fields from Expanded Relationships

When using $expand, select specific fields from related entities:

# L Full expansion (large response)
curl "https://oda.ft.dk/api/Sag?%24expand=SagAktør/Aktør&%24top=50"

#  Selective expansion
curl "https://oda.ft.dk/api/Sag?%24select=titel,SagAktør/Aktør/navn&%24expand=SagAktør/Aktør&%24top=50"

Common Field Selection Patterns

# Basic case information
curl "https://oda.ft.dk/api/Sag?%24select=id,titel,offentlighedskode,opdateringsdato"

# Voting results with politician names
curl "https://oda.ft.dk/api/Stemme?%24select=id,typeid,Afstemning/konklusion,Aktør/navn&%24expand=Afstemning,Aktør"

# Document metadata without full text
curl "https://oda.ft.dk/api/Dokument?%24select=titel,offentlighedskode,Fil/filurl,Fil/format&%24expand=Fil"

Expansion Optimization

Strategic Use of $expand

Only expand relationships when you need the related data. Each expansion increases response size and processing time.

Performance Impact

Multi-level expansions like $expand=Stemme/Aktør on voting data can increase response time from 200ms to 1.8s for 100 records.

Guidelines: - Expand only required relationships - Use $select with expansions to limit returned fields - Consider separate queries for deeply nested data

Expansion Performance Comparison

Query Type Response Time Use Case
Basic entity 85-200ms ID lookups, lists
Single expansion 200-500ms Related data needed
Multi-level expansion 800ms-2.1s Complex relationships
# L Unnecessary expansion
curl "https://oda.ft.dk/api/Sag?%24expand=SagAktør/Aktør&%24top=100"
# Use only if you need politician information

#  Targeted expansion
curl "https://oda.ft.dk/api/Sag?%24expand=Sagskategori&%24top=100"
# Lightweight category information

Filtering Optimization

Apply Filters Early

Filters should be applied before expansions to reduce the dataset size being processed.

#  Filter first, then expand
curl "https://oda.ft.dk/api/Sag?%24filter=year(opdateringsdato)%20eq%202025&%24expand=Sagskategori&%24top=50"

# L Expanding unnecessarily large datasets
curl "https://oda.ft.dk/api/Sag?%24expand=SagAktør/Aktør&%24filter=year(opdateringsdato)%20eq%202025&%24top=50"

Index-Friendly Filtering

Use filters that align with likely database indexes for better performance:

Fast Filters (likely indexed): - id eq 12345 - year(opdateringsdato) eq 2025 - offentlighedskode eq 'O' - typeid eq 1

Slower Filters: - substringof('text', titel) - Text search - Complex date ranges - Multiple OR conditions

#  Fast: ID and type filters
curl "https://oda.ft.dk/api/Stemme?%24filter=afstemningid%20eq%20123%20and%20typeid%20eq%201"

#   Slower: Text search
curl "https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)"

Critical: Filter Validation

Silent Filter Failures

The API silently ignores invalid filter field names and returns unfiltered data. A typo in a field name will return the complete dataset instead of an error.

# L Typo in field name - returns ALL records silently
curl "https://oda.ft.dk/api/Sag?%24filter=titl%20eq%20'test'&%24top=5"
# Returns 5 random records instead of error

#  Correct field name
curl "https://oda.ft.dk/api/Sag?%24filter=titel%20eq%20'test'&%24top=5"
# Returns proper filtered results

Detection Strategy: Monitor result counts. If you get 100 results when expecting fewer, check your filter syntax.

Pagination Strategies

Optimal Page Sizes

Based on performance testing:

Page Size Use Case Performance
25-50 Real-time display Excellent (85-200ms)
100-200 Data processing Good (200-500ms)
500-1000 Bulk analysis Acceptable (1-2s)
1000+ Batch processing Monitor carefully
#  Efficient pagination
curl "https://oda.ft.dk/api/Sag?%24top=100&%24skip=0&%24select=id,titel"
curl "https://oda.ft.dk/api/Sag?%24top=100&%24skip=100&%24select=id,titel"

Count Strategy

Use $inlinecount=allpages sparingly as it adds processing overhead:

#  For pagination UI
curl "https://oda.ft.dk/api/Sag?%24filter=year(opdateringsdato)%20eq%202025&%24inlinecount=allpages&%24top=1"

# L For every page request
curl "https://oda.ft.dk/api/Sag?%24inlinecount=allpages&%24top=100&%24skip=100"

Query Batching Approaches

Sequential vs Parallel Queries

For multiple related queries, consider the tradeoffs:

Sequential Queries (Recommended):

# Get case IDs first
curl "https://oda.ft.dk/api/Sag?%24filter=substringof('klima',titel)&%24select=id"

# Then get detailed data for specific cases
curl "https://oda.ft.dk/api/Sag?%24filter=id%20eq%20123&%24expand=SagAktør/Aktør"

Parallel Queries:

# Run simultaneously in different terminals/processes
curl "https://oda.ft.dk/api/Sag?..." &
curl "https://oda.ft.dk/api/Aktør?..." &
curl "https://oda.ft.dk/api/Afstemning?..." &

Caching Strategies

Client-Side Caching

The API sends aggressive no-cache headers, but you can implement client-side caching:

// Client-side cache implementation
class ODACacheClient {
    constructor() {
        this.cache = new Map();
        this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
    }

    async get(url) {
        const cached = this.cache.get(url);
        if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
            return cached.data;
        }

        const response = await fetch(url);
        const data = await response.json();

        this.cache.set(url, {
            data: data,
            timestamp: Date.now()
        });

        return data;
    }
}

Smart Caching Patterns

Cache by Data Freshness:

import time
from datetime import datetime, timedelta

class SmartODACache:
    def should_cache(self, entity_type, query_params):
        # Cache static data longer
        if entity_type in ['Aktør', 'Periode', 'Sagskategori']:
            return timedelta(hours=24)

        # Cache recent data briefly
        if '$filter' in query_params and 'opdateringsdato' in query_params:
            return timedelta(minutes=15)

        # Default short cache
        return timedelta(minutes=5)

Before/After Optimization Examples

Example 1: Parliamentary Case Analysis

L Unoptimized Query (2.1s, 450KB)

curl "https://oda.ft.dk/api/Sag?%24expand=SagAktør/Aktør,Sagstrin,SagDokument/Dokument&%24top=100"

 Optimized Query (0.3s, 45KB)

# Step 1: Get filtered cases
curl "https://oda.ft.dk/api/Sag?%24filter=year(opdateringsdato)%20eq%202025&%24select=id,titel,opdateringsdato&%24top=100"

# Step 2: Get specific relationships as needed
curl "https://oda.ft.dk/api/SagAktør?%24filter=sagid%20eq%20123&%24select=rolleid,Aktør/navn&%24expand=Aktør"

Performance Improvement: 85% faster, 90% less bandwidth

Example 2: Voting Analysis

L Unoptimized Query (1.8s, 280KB)

curl "https://oda.ft.dk/api/Stemme?%24expand=Afstemning,Aktør&%24top=200"

 Optimized Query (0.4s, 55KB)

curl "https://oda.ft.dk/api/Stemme?%24filter=typeid%20eq%201&%24select=id,typeid,Afstemning/konklusion,Aktør/navn&%24expand=Afstemning,Aktør&%24top=200"

Performance Improvement: 78% faster, 80% less bandwidth

L Unoptimized Query (1.2s, 320KB)

curl "https://oda.ft.dk/api/Dokument?%24expand=DokumentAktør/Aktør,Fil&%24filter=substringof('budget',titel)&%24top=50"

 Optimized Query (0.35s, 65KB)

curl "https://oda.ft.dk/api/Dokument?%24filter=substringof('budget',titel)&%24select=titel,offentlighedskode,Fil/filurl,Fil/format&%24expand=Fil&%24top=50"

Performance Improvement: 71% faster, 80% less bandwidth

Performance Monitoring

Key Metrics to Track

  1. Response Time: Aim for <500ms for interactive queries
  2. Response Size: Monitor bandwidth usage
  3. Result Count: Watch for silent filter failures (unexpected 100+ results)
  4. Cache Hit Rate: If implementing client caching

Error Detection

// Monitor for silent filter failures
async function queryWithValidation(url, expectedMaxResults = 50) {
    const response = await fetch(url);
    const data = await response.json();

    // Check for suspiciously large result sets
    if (data.value && data.value.length >= 100 && expectedMaxResults < 100) {
        console.warn('Possible silent filter failure - received default 100 results');
        console.warn('Check filter field names for typos');
    }

    return data;
}

Best Practices Summary

  1. Always use $select: Reduces response size by 70-90%
  2. Filter before expanding: Apply filters first to reduce dataset size
  3. Limit expansions: Only expand relationships you actually need
  4. Monitor result counts: Watch for silent filter failures
  5. Use appropriate page sizes: 25-100 for interactive, 100-500 for processing
  6. Implement client caching: API has no-cache headers but client caching helps
  7. Validate filter fields: Double-check field names to avoid silent failures
  8. Consider sequential queries: Sometimes better than complex expansions

Optimization Results

Following these guidelines typically achieves:

  • 70-85% reduction in response time
  • 80-90% reduction in bandwidth usage
  • Improved reliability through better error detection
  • Better user experience with faster loading