A FlowScript sequence value represents an ordered list of elements. This document describes how to write query expressions to filter, order, group, join and modify such sequences.
Remember that all values in FlowScript are immutable. You cannot change a sequence value; you can only make modified copies of it.
Advanced: A note on laziness
All the query operators described in this document express lazy sequences. This means that an expression such as users where name == "joel" does not perform the work of filtering the sequence immediately. Instead, the filtering happens on-demand whenever the elements of the sequence are needed.
This has several benefits:
Queries can be efficiently composed: you can filter or transform a sequence in several steps without incurring the performance penalty of creating the intermediate sequences.
An expression such as first(users where active == 1) becomes much more efficient, as the filtering operation can stop when the first active user is found.
However, it laziness can also degrade performance in situations where the elements of a sequence are enumerated more than once, such as:
looping over a queried sequence several times, or
calling functions such as first or last on a queries sequence several times.
In those cases, you can use the eval function to materialize the sequence.
// Example: 'activeUsers' is a lazy sequence,// so it is more efficient to materialize it using eval// before accessing its first and last elements.let activeUsers =eval(users where active ==1);let firstActiveUser =first(activeUsers);let lastActiveUser =last(activeUsers);
Filtering
To filter a sequence of records, use the where operator. This operator takes a sequence expression on the left and a filter expression on the right. The resulting values contains only the records for which the filter expression evaluates to true.
let users = [ { name:"yohannes", active:1 }, { name:"mira", active:0 }, { name:"sosuke", active:1 }];let activeUsers = users where active ==1;
As demonstrated in the example above, the filter expression has access to each member in the sequence's records as if they were local variables. The expression active == 1, denoting a boolean value indicating whether a user's "active" flag is equal to one, is written from a perspective "inside" a record.
It is also possible to use the where operator with an alias.
Ordering
To order a sequence of records, use the order by operator, which takes a sequence expression on the left and a comma-separated list of sort specifiers on the right. In the example below, the countries are sorted by population, descending order, and secondarily by name in ascending order (making sure the Netherlands and Zambia, having the same population size in this example, come in alphabetical order).
let countries = [ { name: "Sweden", populationInMillions: 10 }, { name: "Zambia", populationInMillions: 19 }, { name: "Netherlands", populationInMillions: 19 }, { name: "United States", populationInMillions: 331 }, { name: "China", populationInMillions: 1441 }, { name: "Brazil", populationInMillions: 213 }];return countries order by populationInMillions desc, nameasc
Select queries
FlowScript supports an SQL-like query syntax for sequences of records. The example below queries a sequence of countries, transforming the populationInMillions field of each record into a member labelled "population", ordering the result by the new population member in ascending order.
Select queries can select, filter, group, join and sort records using the select, where, group by, join and order by clauses. Only the select clause is mandatory.
The select clause
The select clause specifies the format of the query result. A select query starts with the keyword select followed by a comma-separated list of expressions giving the members to include, the from keyword, and a source expression. If the wildcard (*) symbol is included in the list of member expressions, all members of the source table are included:
returnselect*from countries;
A member expression may be implicitly or explicitly labelled. Complex expressions (including more than one identifier) must be explicitly labelled. Explicitly labelled members are declared using the as keyword. In the following example, the "name" member is implicitly labelled and will have the label "name" in the resulting sequence of records. The second member is explicitly labelled "population". Because there is no wildcard symbol, the populationInMillions member will not be a part of the result.
The group by clause is used to group rows that share some values into summary rows, typically in combination with aggregation functions like sum, count, etc.
The following example takes a list of sales transactions and groups them by the sold product, summing the quantity and price for each product. It also includes the customerID members without any grouping.
Join clauses are used to combine records from two sequences based one some relationship between the records. In the example below, each sales transaction is paired with the name of the corresponding customer. The last transaction (with ID 6) has no curresponding customer, so its customer name will be null.
Left join will return all elements from the left sequence and all matching records in the right sequence. Where no matching element is found on the right, fields selected from the right will be null.
A mirror image of the left join, right join will return all elements from the right sequence and all matching records in the left sequence. Where no matching element is found on the left, fields selected from the left will be null.
All the different query types support aliases, which is useful if the records in the queries sequence contain some member whose label is the same as the name of some variable.
let id =1;let users = [ { id:0, name:"Jim" }, { id:1, name:"Joe" }];returnfirst(users asuwhereu.id = id);
Advanced: Functional-style queries
The different types of query syntax described above are well suited for table-like sequences of records. It is less well suited for nested structures, sequences of simple values or sequences of sequences. For these cases, you can use functional-style queries using functions defined in the Seq module.
In the example below, each customer record contains its own list of transactions. A functional-style query is used to find the sum of the total price of all transactions for all customers.
open Seq;let customers = [ { customerId:101, name:"Aaron", email:"aaron@example.com", transactions: [ { id:9, product:"Smartphone", quantity:1, totalPrice:800 } ] }, { customerId:102, name:"Felicia", email:"felicia@example.com", transactions: [ { id:10, product:"Tablet", quantity:2, totalPrice:1500 }, { id:11, product:"Laptop", quantity:1, totalPrice:200 } ] },];returncustomers.collect(c =>c.transactions) // get all customer transactions.map(t =>t.totalPrice) // get the total price for each transaction.sum(); // sum the sequence of numbers