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
$selectto fetch only needed fields (reduces response size by 70-90%) - Apply
$filterbefore$expandto process fewer records - Limit
$expandto 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
Optimized: Selected Fields Only
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.
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)
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)
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
Example 3: Document Search¶
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¶
- Response Time: Aim for <500ms for interactive queries
- Response Size: Monitor bandwidth usage
- Result Count: Watch for silent filter failures (unexpected 100+ results)
- 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¶
- Always use $select: Reduces response size by 70-90%
- Filter before expanding: Apply filters first to reduce dataset size
- Limit expansions: Only expand relationships you actually need
- Monitor result counts: Watch for silent filter failures
- Use appropriate page sizes: 25-100 for interactive, 100-500 for processing
- Implement client caching: API has no-cache headers but client caching helps
- Validate filter fields: Double-check field names to avoid silent failures
- 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