Introduction to ArangoDB Query Language
ArangoDB Query Language (AQL) is the primary query language for ArangoDB, a multi-model NoSQL database system that supports document, graph, and key-value data models. AQL allows you to retrieve, manipulate, and transform data stored in ArangoDB using a syntax similar to SQL but optimized for working with documents and graphs. Mastering AQL is essential for efficiently working with ArangoDB and leveraging its full power across different data models.
Core AQL Concepts
Basic Structure of an AQL Query
FOR variable IN collection
FILTER condition
SORT variable.attribute DIRECTION
LIMIT offset, count
RETURN projection
Data Types in AQL
| Type | Description | Example |
|---|
| Null | Null value | null |
| Boolean | True or false | true, false |
| Number | Numeric values | 42, 3.14159 |
| String | Text values | "Hello", 'World' |
| Array | Ordered list | [1, 2, 3] |
| Object | Document/object | { "name": "John" } |
Document Queries
Basic Data Retrieval
// Retrieve all documents from a collection
FOR doc IN users
RETURN doc
// Retrieve specific fields
FOR doc IN users
RETURN { name: doc.name, email: doc.email }
Filtering Data
// Basic filter
FOR doc IN users
FILTER doc.age >= 18
RETURN doc
// Multiple conditions
FOR doc IN users
FILTER doc.age >= 18 AND doc.status == "active"
RETURN doc
// OR condition
FOR doc IN users
FILTER doc.country == "USA" OR doc.country == "Canada"
RETURN doc
// Existence check
FOR doc IN users
FILTER doc.email != null
RETURN doc
Sorting Results
// Single attribute ascending
FOR doc IN users
SORT doc.name ASC
RETURN doc
// Multiple attributes
FOR doc IN users
SORT doc.age DESC, doc.name ASC
RETURN doc
Limiting Results
// First 10 results
FOR doc IN users
LIMIT 10
RETURN doc
// Skip 20, take 10 (pagination)
FOR doc IN users
LIMIT 20, 10
RETURN doc
Graph Queries
Traversing Graphs
// Basic traversal
FOR vertex, edge, path IN 1..3 OUTBOUND 'users/john' friendships
RETURN vertex
// With filtering
FOR vertex, edge, path IN 1..3 OUTBOUND 'users/john' friendships
FILTER vertex.age > 30
RETURN vertex
Shortest Path
FOR path IN OUTBOUND SHORTEST_PATH 'users/john' TO 'users/jane' friendships
RETURN path
All Paths
FOR path IN OUTBOUND ANY_SHORTEST_PATH 'users/john' TO 'users/jane' friendships
RETURN path
Advanced Techniques
Joins
// Joining collections
FOR user IN users
FOR order IN orders
FILTER order.user_id == user._key
RETURN { user: user.name, order: order.id }
Aggregations
// Count
RETURN COUNT(
FOR doc IN users
FILTER doc.active == true
RETURN 1
)
// Group by with aggregation
FOR doc IN sales
COLLECT category = doc.category WITH COUNT INTO count
RETURN { category, count }
// Complex aggregation
FOR doc IN orders
COLLECT region = doc.region, year = DATE_YEAR(doc.date)
AGGREGATE total = SUM(doc.amount)
RETURN { region, year, total }
Subqueries
FOR user IN users
LET userOrders = (
FOR order IN orders
FILTER order.user_id == user._id
RETURN order
)
RETURN { user: user.name, orders: userOrders }
Data Manipulation
Insert Operations
// Insert single document
INSERT {
name: "John",
email: "john@example.com"
} INTO users
// Insert multiple documents
FOR user IN [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" }
]
INSERT user INTO users
Update Operations
// Update single document
UPDATE "12345" WITH {
status: "inactive"
} IN users
// Update multiple documents
FOR user IN users
FILTER user.age < 18
UPDATE user WITH {
minor: true
} IN users
Replace Operations
// Replace document (overwrites entire document)
REPLACE "12345" WITH {
name: "John Doe",
email: "john@example.com",
age: 35
} IN users
Remove Operations
// Remove single document
REMOVE "12345" IN users
// Remove multiple documents
FOR user IN users
FILTER user.status == "deleted"
REMOVE user IN users
Upsert Operations
// Insert if not exists, update if exists
UPSERT { name: "John" }
INSERT { name: "John", created: DATE_NOW() }
UPDATE { updated: DATE_NOW() }
IN users
Common Functions
String Functions
| Function | Description | Example |
|---|
CONCAT() | Concatenate strings | CONCAT("Hello", " ", "World") |
LOWER() | Convert to lowercase | LOWER("TEXT") |
UPPER() | Convert to uppercase | UPPER("text") |
SUBSTRING() | Extract substring | SUBSTRING("abcdef", 1, 3) |
CONTAINS() | Check if string contains | CONTAINS("abc", "b") |
LENGTH() | String length | LENGTH("text") |
Numeric Functions
| Function | Description | Example |
|---|
ABS() | Absolute value | ABS(-5) |
CEIL() | Round up | CEIL(3.4) |
FLOOR() | Round down | FLOOR(3.7) |
ROUND() | Round to nearest | ROUND(3.5) |
RAND() | Random number | RAND() |
MAX() | Maximum value | MAX(2, 3, 1) |
MIN() | Minimum value | MIN(2, 3, 1) |
Date Functions
| Function | Description | Example |
|---|
DATE_NOW() | Current date/time | DATE_NOW() |
DATE_ISO8601() | Format as ISO8601 | DATE_ISO8601(DATE_NOW()) |
DATE_YEAR() | Extract year | DATE_YEAR(DATE_NOW()) |
DATE_MONTH() | Extract month | DATE_MONTH(DATE_NOW()) |
DATE_DAY() | Extract day | DATE_DAY(DATE_NOW()) |
Common Challenges and Solutions
Performance Optimization
| Challenge | Solution |
|---|
| Slow queries | Create indexes on frequently filtered fields |
| Large result sets | Use LIMIT and pagination |
| Complex joins | Consider denormalizing data or using graph relationships |
| Slow aggregations | Use indexed fields for aggregation keys |
Working with Indexes
// Create index
db.users.ensureIndex({ type: "hash", fields: ["email"] })
// Create composite index
db.users.ensureIndex({ type: "hash", fields: ["country", "city"] })
// Create geo index
db.locations.ensureIndex({ type: "geo", fields: ["coordinates"] })
// Create fulltext index
db.products.ensureIndex({ type: "fulltext", fields: ["description"] })
Error Handling
// Try-catch in AQL
TRY
FOR doc IN collection
RETURN doc
CATCH
RETURN { error: true, message: "An error occurred" }
Best Practices
Query Optimization
- Always use indexes for filtered fields
- Limit result sets to necessary documents
- Avoid large in-memory operations
- Use bind parameters for dynamic values
- Monitor query execution plans with EXPLAIN
Data Modeling
- Choose appropriate collection types (document vs. edge)
- Consider embedding related data for frequently joined information
- Use graph relationships for complex connections
- Keep document size manageable (< 64MB)
- Use sensible _key values for better performance
Security Considerations
- Always validate and sanitize user input
- Use bind parameters to prevent injection attacks
- Apply proper access controls at collection level
- Avoid exposing internal document IDs to users
- Use ArangoDB’s security features (roles, permissions)
AQL vs SQL Comparison
| SQL | AQL | Notes |
|---|
SELECT * FROM users | FOR u IN users RETURN u | Basic retrieval |
SELECT * FROM users WHERE age >= 18 | FOR u IN users FILTER u.age >= 18 RETURN u | Filtering |
SELECT * FROM users ORDER BY name ASC | FOR u IN users SORT u.name ASC RETURN u | Sorting |
SELECT * FROM users LIMIT 10 | FOR u IN users LIMIT 10 RETURN u | Limiting |
SELECT u.name, o.id FROM users u JOIN orders o ON u.id = o.user_id | FOR u IN users FOR o IN orders FILTER o.user_id == u._key RETURN {name: u.name, order: o.id} | Joins |
SELECT COUNT(*) FROM users | RETURN LENGTH(users) | Count |
INSERT INTO users VALUES ('John', 'john@ex.com') | INSERT {name: "John", email: "john@ex.com"} INTO users | Insert |
UPDATE users SET status = 1 WHERE id = 123 | UPDATE "123" WITH {status: 1} IN users | Update |
DELETE FROM users WHERE id = 123 | REMOVE "123" IN users | Delete |
Resources for Further Learning
Official Documentation
Learning Resources
Community Resources
Tools
Remember that ArangoDB’s multi-model approach allows you to combine document, graph, and key-value operations in a single query, providing exceptional flexibility and power for complex data operations.