5

I'm using childByAutoId() to generate my children. Each child looks like:

{
    user_id: 1
}

I'd like to get the last 10 most recently added, sorted by time DESC. What's the easiest way to do this?

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
TIMEX
  • 259,804
  • 351
  • 777
  • 1,080
  • Synchronizing the 10 most recent children on a location is `ref.queryOrderedByKey().queryLimitedToLast(10)`. But that probably means I'm misunderstanding something about your question. Can you elaborate on what your data looks like (JSON as text, no screenshots please), what your code looks like and what is giving problems? – Frank van Puffelen Apr 13 '16 at 14:33
  • Did you downvote all the answers? If so, can you leave a comment with each answer as to why it wasn't not helpful? Answering the question in my earlier comment would also go a long way to allowing us to help you. For example, there is no timestamp/time in your pseudo-json. – Frank van Puffelen Apr 17 '16 at 22:59

3 Answers3

17

The answer is that you need to use a bit of reverse logic, and also store a timestamp key:value pair within each node as a negative value. I omitted the user_id: 1 to keep the answer cleaner.

Here's the Firebase structure

"test" : {
    "-KFUR91fso4dEKnm3RIF" : {
      "timestamp" : -1.46081635550362E12
    },
    "-KFUR9YH5QSCTRWEzZLr" : {
      "timestamp" : -1.460816357590991E12
    },
    "-KFURA4H60DbQ1MbrFC1" : {
      "timestamp" : -1.460816359767055E12
    },
    "-KFURAh15i-sWD47RFka" : {
      "timestamp" : -1.460816362311195E12
    },
    "-KFURBHuE7Z5ZvkY9mlS" : {
      "timestamp" : -1.460816364735218E12
    }
  }

and here's how that's written out to Firebase; I just used a IBAction for a button to write out a few nodes:

let testRef = self.myRootRef.childByAppendingPath("test")

let keyRef = testRef.childByAutoId()

let nodeRef = keyRef.childByAppendingPath("timestamp")

let t1 = Timestamp

nodeRef.setValue( 0 - t1) //note the negative value

and the code to read it in

    let ref = self.myRootRef.childByAppendingPath("test")
    ref.queryOrderedByChild("timestamp").queryLimitedToFirst(3).observeEventType(.ChildAdded, withBlock: { snapshot in
        print("The key: \(snapshot.key)") //the key
    })

and I declared a little function to return the current Timestamp

var Timestamp: NSTimeInterval {
    return NSDate().timeIntervalSince1970 * 1000
}

and the output

The key: -KFURBHuE7Z5ZvkY9mlS
The key: -KFURAh15i-sWD47RFka
The key: -KFURA4H60DbQ1MbrFC1

As you can see, they are in reverse order.

Things to note:

  1. Writing out your timestamp as negative values
  2. When reading in use .queryLimitedToFirst instead of last.

On that note, you can also just read the data as usual and add it to an Array then then sort the array descending. That puts more effort on the client and if you have 10,000 nodes may not be a good solution.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • 1
    How do you store `FIRServerValue.timestamp()` as negative value? I can't just do `0 - FIRServerValue.timestamp()` since `timestamp()` is `[AnyHashable: Any]` object... Any help would be appreciated. – slow Nov 11 '16 at 21:24
  • @MoreCode If you review the question and the answer, the timestamp is generated in the client app and not relying on the FIRServerValue.timestamp(). Please post a separate question as it's a different answer which involves the server timestamp value. Post that and I will post solution! – Jay Nov 12 '16 at 16:47
  • will queryOrderedByChild("timestamp") be slow when you have 100K of items? give that they are reversely stored and need to be reversely sorted each time? – Muhammad Hassan Nasr Nov 13 '16 at 17:54
  • @MuhammadHassan Not sure I understand the question. If you store the timestamps as in my answer they are stored in reverse order (descending) so they would not need to be reversed each time. My comment at the end states as an alternative, read in the all the nodes and sort in code - but that may not be good for a large amount of nodes. Most importantly however, if you need to read in 100k items at one time, there may be a better way to structure your data! – Jay Nov 13 '16 at 18:07
  • @Jay what I understand is that items are stored in the order your write them, so they are not stored sorted unless you insert them as so. Your code uses queryOrderedByChild("timestamp").queryLimitedToFirst(3) which is performed in the server and it is not aware of how they are sorted, so it will sort them each time and get the first 3. So lets assume I want to get the last. I assume it will get slow as your grow unless you put indexOn(timestamp) – Muhammad Hassan Nasr Nov 21 '16 at 11:36
  • @MuhammadHassan I didn't really mean they are 'stored' in any particular fashion. What I was driving at is they can be accessed in reverse order without having to sort them in code; we're letting Firebase do the work at the server level and feeding sorted data to the app. That will be a LOT faster than trying to read them in and sort in code. And to answer the comment about queryOrderedByChild("timestamp") for 100k items... Firebase can do that easily, and depending on the results of the query, it's very fast. We've loaded 10's of thousands of nodes and noticed no speed issues what-so-ever. – Jay Nov 21 '16 at 18:54
  • Why bother making it negative? When one could get the most recent just by doing: .queryOrdered(byChild: "timeStamp") .queryLimited(toLast: 10) And then doing .reversed() on the returned array –  Jun 27 '19 at 20:55
  • .. which would present the data in ascending order, not descending , and while you can reverse in code, if you want to get say, the last 1000 nodes, it could overwhelm the device. Read through my comment above as it explains the concept more in depth. – Jay Jun 27 '19 at 20:56
2

I'm assuming your data actually looks like this:

someDataSet: {
    longUID-1: {
        timeCreated: 9999999999, // (seconds since the javascript epoch)
        user_id: 1
    },
    longUID-2: {
        timeCreated: 1111111111,
        user_id: 2
    },
    longUID-3: {
        timeCreated: 3141592653,
        user_id: 3
    }
}

You could automate that by calling Firebase.push({user_id: ###, timeCreated: ###}) multiple times in a for loop or any other method. Maybe you're adding news stories to a webpage, but you only want your user to see the most current stories--- IDK. But the answer to your question is to use Firebase's ref.orderByChild() and ref.limitToLast().

var ref = new Firebase("<YOUR-FIREBASE-URL>.firebaseio.com/someDataSet"); 
    //the "/someDataSet" comes from the arbitrary name that I used up above

var sortedRef = ref.orderByChild('timeCreated');
    //sort them by timeCreated, ascending

sortedRef.limitToLast(2).on("child_added", function(snapshot){
    var data = snapshot.val();

    console.log(data);

    /* do something else with the data */
});

//The console would look like this

// Object {timeCreated: 9999999999, user_id: 1}
// Object {timeCreated: 3141592653, user_id: 3}

This happened because the program took the child with the greatest timeCreated value first and then the second greatest (value) second... Also note, the longUID means nothing when you sort them by child and neither do the other values (user_id in this case)

Here is the documentation for:

Frozenfrank
  • 61
  • 1
  • 7
  • Good answer but the OP wanted descending order, not ascending. – Jay Apr 16 '16 at 13:57
  • 1
    Also the ref.orderByChild('timeCreated') is not valid code as well as some of the other code. This is an iOS question. – Jay Apr 16 '16 at 14:03
  • 1
    @jay I sorted them ascending but then took the **LAST** few elements. By definition, that would mean that I have the elements with the greatest values... – Frozenfrank Apr 26 '16 at 22:38
  • @jay you are right however, I wrote my code for the browser- the platform that I have been using. – Frozenfrank Apr 26 '16 at 22:42
1

The code: ref.queryOrderedByKey().queryLimitedToLast(10) can be used for getting the most recent 10 data. However, this is an ascending order by default.

Alternatively, you can order your data via

ref.orderByChild("id").on("child_added", function(snapshot) {
  console.log(snapshot.key());
});

This also presents an ascending order by default. To change it into descending order is little bit tricky. What I would suggest it to multiply ids by -1 as shown below and then sort them.

var ref= new Firebase("your data");
ref.once("value", function(allDataSnapshot) {
  allDataSnapshot.forEach(function(dataSnapshot) {
    var updatedkey = -1 * dataSnapshot.key();
    ref.update({ element: { id: updatedkey}});
  });
});

This two SO page might be useful for you also, please check:

How to delete all but most recent X children in a Firebase node?

firebaseArray descending order?

Community
  • 1
  • 1
Eray Balkanli
  • 7,752
  • 11
  • 48
  • 82
  • That doesn't really answer the question. The OP wants to return X number of children in descending order. The code in this answer returns ALL of the children in ascending order - that can be disastrous if there are 100,000 children on an iOS device as it could exceed the memory. Also, it's an iOS question so Swift or ObjC code should be used. Can you update your answer so it addresses the question? – Jay Apr 16 '16 at 14:53
  • I did not get you my friend. Why cant the OP use "ref.queryOrderedByKey().queryLimitedToLast(10)" after multiplying ids by -1? – Eray Balkanli Apr 16 '16 at 15:01
  • The code posted in the answer reads in everything the node ref.once("value", function(allDataSnapshot) to then multiply by -1. If it's 100,000 nodes that could be an issue. – Jay Apr 16 '16 at 15:05
  • In this case, this code will multiple 100000 nodes by -1 (their ids), then the programmer will be able to reach most recent 10 values by ordering. Then the user should update the data by mutiplying -1 again. Yes, I agree it might affect the performance negatively, but this is a way to provide that OP asks. I am open to hear another alternatives providing better performance.. – Eray Balkanli Apr 16 '16 at 15:10