Work with multiple Sets
This guide covers common patterns for performing SQL-like set operations, such as unions, joins, and intersections, in FQL. You can use these operations to combine and filter Sets of collection documents.
Unions
Use set.concat()
to combine two
Sets into a single Set. This is similar to a UNION
clause in SQL.
// Setup: Get frozen and produce `Category` documents.
let frozenCat = Category.byName("frozen").first()
let produceCat = Category.byName("produce").first()
// Get Sets of related `Product` documents for each category.
let frozenDocs = Product.byCategory(frozenCat)
let produceDocs = Product.byCategory(produceCat)
// Use `concat()` to combine the previous
// `Product` document Sets. Project each
// product's name and category.
frozenDocs.concat(produceDocs) {
name: .name,
category: .category!.name
}
// The combined Set contains all `Product` documents
// in the frozen and produce categories.
{
data: [
{
name: "pizza",
category: "frozen"
},
...
{
name: "cilantro",
category: "produce"
}
]
}
Chain set.concat()
You can chain set.concat()
to combine three or more Sets. The following example expands on
the previous one:
// Setup: Get frozen, produce, and party `Category` documents.
let frozenCat = Category.byName("frozen").first()
let produceCat = Category.byName("produce").first()
let partyCat = Category.byName("party").first()
// Get Sets of related `Product` documents for each category.
let frozenDocs = Product.byCategory(frozenCat)
let produceDocs = Product.byCategory(produceCat)
let partyDocs = Product.byCategory(partyCat)
// Chain `concat()` expressions to combine the previous
// `Product` document Sets. Project each
// product's name and category.
frozenDocs
.concat(produceDocs)
.concat(partyDocs) {
name: .name,
category: .category!.name
}
// The combined Set contains all `Product` documents
// in the frozen, produce, and party categories.
{
data: [
{
name: "pizza",
category: "frozen"
},
{
name: "avocados",
category: "produce"
},
...
{
name: "taco pinata",
category: "party"
}
]
}
Use set.flatMap()
When combining several Sets, you can improve readability
by using set.flatMap()
to iterate through the Sets:
// Start with an Array of category names.
["frozen", "produce", "party"]
// Convert the Array to a Set. `flatMap()` operates on Sets.
.toSet()
// Use `flatMap()` to process each category name
// and combine the resulting Sets. `flatMap()` will:
// 1. Apply the given function to each element of the Set.
// 2. Collect the results in a Set,
// with a nested child Set for each element.
// 3. Flatten the resulting Set by one level,
// resulting in a single, flat Set.
.flatMap(name => {
// Get the `Category` document for each category name.
let category = Category.byName(name).first()
// Get Sets of related `Product` documents for each category.
// Each Set is included in the flattened results, creating
// a single Set of products across all listed categories.
// Project each product's name and category.
Product.byCategory(category)
}) {
name: .name,
category: .category!.name
}
// The combined Set contains all `Product` documents
// in the listed categories.
{
data: [
{
name: "pizza",
category: "frozen"
},
{
name: "avocados",
category: "produce"
},
...
{
name: "taco pinata",
category: "party"
}
]
}
Joins
In Fauna, you can use document
references to model relationships between documents in different collections.
You can use projection to traverse the
references. This is similar to performing a JOIN
in SQL.
// Get a Set of `Order` documents with a `status` of `cart`.
// In production, use an index instead of `where()` for
// better performance.
let orders: Any = Order.where(.status == "cart")
// Projects fields in the `Order` documents.
orders {
// Traverses the `Customer` document reference in
// the `customer` field.
customer {
id,
name,
email
}
}
{
data: [
{
customer: {
id: "111",
name: "Alice Appleseed",
email: "alice.appleseed@example.com"
}
},
{
customer: {
id: "222",
name: "Bob Brown",
email: "bob.brown@example.com"
}
}
]
}
Use set.map()
for transformations
You can also use set.map()
to
extract and transform field values from referenced documents.
// Get a Set of `Order` documents with a `status` of `cart`.
// In production, use an index instead of `where()` for
// better performance.
let orders: Any = Order.where(.status == "cart")
// Uses `map()` to iterate through each document in the Set
// and extract the `customer` field from each `Order` document.
// Then project fields from the `customer` field's referenced
// `Customer` document.
orders.map(order => order.customer) {
id,
name,
email
}
{
data: [
{
id: "111",
name: "Alice Appleseed",
email: "alice.appleseed@example.com"
},
{
id: "222",
name: "Bob Brown",
email: "bob.brown@example.com"
}
]
}
Use set.flatMap()
for *-to-many relationships
For one-to-many or many-to-many relationships, you can use
set.flatMap()
to extract field
values from referenced documents and flatten the resulting Set by one level:
// Get a Set of all `Customer` documents.
Customer.all()
// Use `flatMap()` to iterate through each document in the Set
// and extract fields from:
// - The `Order` document referenced in the `order` field.
// - The `Customer` document reference nested in the `order` field.
// Then flatten the resulting Set.
.flatMap(customer => {
let orders: Any = customer.orders
orders {
id,
total,
status,
customer {
id,
name
}
}
})
{
data: [
{
id: "408922916844994633",
total: 5392,
status: "cart",
customer: {
id: "111",
name: "Alice Appleseed"
}
},
{
id: "408922916872257609",
total: 1880,
status: "cart",
customer: {
id: "222",
name: "Bob Brown"
}
}
]
}
Intersections
An intersection returns elements that exist in multiple Sets,
similar to an INTERSECT
clause in SQL.
Use includes()
for smaller Sets
For small Sets, you can use
set.includes()
to perform a more literal intersection:
// Get Set of `Customer` documents with a cart.
let customersWithCarts = Customer.where(.cart != null)
// Gets Set of `Customer` documents with an address in DC.
let customersInDC = Customer.where(.address.state == "DC")
// Use `where()` and `includes()` to find the intersection
// of the two Sets. This approach is inefficient for large Sets.
customersWithCarts
.where(customer => customersInDC.includes(customer)) {
id,
name,
email
}
Using set.includes()
for large Sets isn’t recommended. For large Sets, this approach
requires a large number of read operations and doesn’t scale well.
Use filters for large Sets
For the best performance with large Sets, use filtering to perform intersections
in FQL. Start with the most selective expression and filter the resulting Sets
using set.where()
:
// Setup: Gets the produce `Category` document.
let produce = Category.byName("produce").first()
// Get `Product` documents that:
// - Are in the produce category.
// - Have a price of $10 or less.
// - Include "organic" in the description.
// Project each product's name and category.
// Use the `byCategory` index to filter products by category.
Product.byCategory(produce)
// Filter products by price.
.where(.price < 10_00) // $10 is USD cents
// Filter products by description
.where(.description.toLowerCase().includes("organic")) {
name: .name,
category: .category!.name
}
Check for the existence of document references
When checking for the existence of
document references, use
set.take()
to avoid unneeded
document reads:
// Get a Set of `Customer` documents that have:
// - An address in "DC".
// - At least one order.
Customer
// Filter customers by state.
.where(.address.state == "DC")
// Filter customers by `orders` field.
// The `orders` field is a computed field containing a Set of
// `Order` documents for the customer. `take(1)` ensures we only
// read the first document to check for existence.
.where(.orders.take(1).isEmpty() == false) {
id,
name,
email
}
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!