Tutorial: Query data subsets

Serverless DatabaseData Structure Query data subsets

Subscriptions may be refined to select only a subset of the watched data nodes, that is, a subset of their child nodes. Such a selections are called queries. For example, it is possible to select only last or first n child nodes of the watched data node, or range of child nodes with respect to their keys (or names).

The ordering of child nodes with respect to their keys, which is used to find out the last or first n ones or a range of them, is formally defined in the Key Ordering section.

Reading the last or first n elements of a list

In JavaScript, you can use the limit() method (to get the last n children) or combine it with the startAt() one (with no argument, to get the first n children) on the data node to watch, before calling the previously presented on() method:

// Reference to our list of contacts
const ref = new Webcom("<your-app>");
const contactsNode = ref.child("contacts");

// Subscribe to last 5 elements of the list
contactsNode.limit(5).on("value", snapshot => {
  const last5Contacts = Object.values(snapshot.val());
  console.log("The last five contacts are now:" , last5Contacts.map(contact => contact.firstName).join(", "));
});

// or:

// Subscribe to first 5 elements of the list
contactsNode.startAt().limit(5).on("value", snapshot => {
  const first5Contacts = Object.values(snapshot.val());
  console.log("The first five contacts are now:" , first5Contacts.map(contact => contact.firstName).join(", "));
});

In Kotlin, the previously presented subscribe(...) method takes an optional constraint parameter (the 2nd one) of type Constraint. The First and Last subclasses make it possible to respectively select the first n or last n children of the watched data node:

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Subscribe to last 5 elements of the list
val subscription = contactsNode.subscribe(ValueChanged.WithData, Last(5)) {
    if (it is WebcomResult.Success) {
        val last5Contacts = it.result.value.convertedAsList<Contact>()
        println("The last five contacts are now: ${last5Contacts?.map { it.firstName }.join(", ")}")
    }
}

// or:

// Subscribe to first 5 elements of the list
val subscription = contactsNode.subscribe(ValueChanged.WithData, First(5)) {
    if (it is WebcomResult.Success) {
        val first5Contacts = it.result.convertedAsList<Contact>()
        println("The first five contacts are now: ${first5Contacts?.map { it.firstName }.join(", ")}")
    }
}

In Swift, the subscribe() method accepts a DatasyncChildrenConstraint value in the chidrenConstraint parameter, which makes it possible to refine the queried data.

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to last 5 elements of the list
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .last(limit: 5)) { event in
    guard let last5Contacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The last five contacts are now:", last5Contacts.map { $0.firstName }.joined(separator: ", "))
}

// or:

// Subscribe to first 5 elements of the list
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .first(limit: 5)) { event in
    guard let first5Contacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The first five contacts are now:", first5Contacts.map { $0.firstName }.joined(separator: ", "))
}

Reading a range of keys

Before querying a range of child nodes, please read the Key Ordering section to understand how the keys of child nodes (ie their names) are sorted under a data node.

In JavaScript, you can combine the startAt() and endAt() methods on the data node to watch before calling the on() method:

// Reference to our list of contacts
const ref = new Webcom("<your-app>");
const contactsNode = ref.child("contacts");

// Subscribe to the elements between "a" and "l" (inclusive)
contactsNode.startAt("a").endAt("l").on("value", snapshot => {
    const rangeContacts = Object.values(snapshot.val());
    console.log("Contacts between 'a' and 'l' are now:", rangeContacts.map(contact => contact.firstName).join(", "))
});

In Kotlin, the Constraint class has the Between sub-class to represent a query of a range of keys (lower and upper bound keys are always included):

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Subscribe to the elements between "a" and "l" (inclusive)
val subscription = contactsNode.subscribe(ValueChanged.WithData, Between("a", "l")) {
    if (it is WebcomResult.Success) {
        val rangeContacts = it.result.value.convertedAsList<Contact>()
        println("Contacts between 'a' and 'l' are now: ${rangeContacts?.map { it.firstName }.join(", ")}")
    }
}

In Swift, simply use the DatasyncChildrenConstraint value suiting your range constraint: startAt(), endAt() or between().

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to the elements between "a" and "l" (inclusive)
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .between(startKey: "a", endKey: "l")) { event in
    guard let rangeContacts: [Contact] = event.value.decoded() else {
        return
    }
    print("Contacts between 'a' and 'l' are now:", rangeContacts.map { $0.firstName }.joined(separator: ", "))
    // => jimix
}

Reading from a key with a limit

It is possible to query the n child nodes after or before a given key (child name). Such a query is still computed along the key order definition.

In JavaScript, you can combine either the startAt() or endAt() methods with the limit() one on the data node to watch, before calling the on() method:

// Reference to our list of contacts
const ref = new Webcom("<your-app>");
const contactsNode = ref.child("contacts");

// Subscribe to the 2 elements from "k"
contactsNode.startAt("k").limit(2).on("value", snapshot => {
    const twoFromKContacts = Object.values(snapshot.val());
    console.log("The 2 contacts from 'k' are now:", twoFromKContacts.map(contact => contact.firstName).join(", "))
});

In Kotlin, the Constraint class has the StartAt a EndAt sub-classes, whose limit property represents how many child nodes you query respectively after the startKey property or before the endKey property (lower and upper bound keys are always included):

// Reference to our list of contacts
val app = WebcomApplication.default
val manager = app.datasyncService.createManager()
val contactsNode = manager / "contacts"

// Subscribe to the 2 elements from "k"
val subscription = contactsNode.subscribe(ValueChanged.WithData, StartAt("k", 2)) {
    if (it is WebcomResult.Success) {
        val twoFromKContacts = it.result.value.convertedAsList<Contact>()
        println("The 2 contacts from 'k' are now: ${twoFromKContacts?.map { it.firstName }.join(", ")}")
    }
}

In Swift, simply use the startAt(key:limit:) constraint value.

// Reference to our list of contacts
let manager = Webcom.defaultApplication.datasyncService.createManager()
let contactsNode = manager / "contacts"

// Subscribe to the 2 elements from "k"
contactsNode?.subscribe(to: .valueChange, childrenConstraint: .startAt(key: "k", limit: 2)) { event in
    guard let twoFromKContacts: [Contact] = event.value.decoded() else {
        return
    }
    print("The 2 contacts from 'k' are now:", twoFromKContacts.map { $0.firstName }.joined(separator: ", "))
    // => lennon, macca
}