4

In this question Firebase Firestore - OR query they said there is no OR query. This means I can't easily query for items that have an ID that is in an array I got earlier.

// What I want
documentRef
    .whereField("itemId", isEqualTo: "id1")
    .orWhereField("itemId", isEqualTo: "id3")

// or
documentRef
    .whereField("itemId", in: idArray)

Since this doesn't work, what option is better to query my data?


  1. Get all the data and check locally

I could get all my records and just check the IDs on the device but the problem is that it can be up to 1000 records and the user might only need three of them.


  1. Do separate queries for all items

I could just do a query whereField("itemId", isEqualTo: "id") for each item that the user needs. This would be good if the user only needs a few of the items but it is possible the user needs all the items and this solution would become a lot slower than the first one. (unless you could batch reads)


  1. Move data

I think I could move the purchased data to the Item collection: Items/itemid/PurchasedBy/uid but I want to retrieve the Item that a user has bought and I don't know how to query an Item by a field inside a nested collection. I also don't know if this would be the correct place for the data.


My Data:

Users/uid/PurchasedItems/itemid

    payedInMoney: "$0.00"
    payedInCoins: "100"
    itemId: "itemid"
    timeStamp: timestamp

Items/itemid

    itemId: "itemid"
    // Other item info

Edit:

I made the first option because that is how I did it on RTDB but even with persistence on and using addSnapshotListener Firestore doesn't even come close to the speed of the RTDB implementation. I clocked the times and it looks like Firestore is at least 4x as slow as RTDB (both with persistence enabled).

// iPad 5 (2017) - iOS 11
RTDB:  50-100ms
FST:  350-450ms

// iPad Air (2014) - iOS 10
RTDB: 100-150ms
FST:  700-850ms

// iPad Mini (2012) - iOS 9
RTDB: 150-200ms
FST:  2900-3200ms

Edit 2:

Decided to stick with RTDB until persistence on Firestore really works.

Edit 3:

The accepted answer fixes the problem in this question and even though it doesn't fix all mine, it might help someone else.

After tests using only one query with Firestore the result still takes longer to come through than doing the nested query in RTDB so I will stick with my decision for now.

Casper Zandbergen
  • 3,419
  • 2
  • 25
  • 49
  • What is the data you are trying to display? All items purchased by a given user? What data from /items/itemid do you need in this query? – Sam Stern Oct 18 '17 at 17:11
  • @hatboysam Indeed trying to show purchased items of a user, I get all the data from the item at once there so I don't need to make another call when the user clicks on the item. – Casper Zandbergen Oct 19 '17 at 06:48

2 Answers2

1

This seems like a good case for duplicating some of the item data. I assume that some item properties like "name" are immutable and can therefore be stored in the PurchasedItems subcollection without worrying about future updates.

So if you add some of the data to PurchasedItems:

Users/uid/PurchasedItems/itemid

    // Existing 
    payedInMoney: "$0.00"
    payedInCoins: "100"
    itemId: "itemid"
    timeStamp: timestamp

    // Add these
    name: "Item Name"
    url: "http://foo.bar/123"

Then you will have enough information to display the list of a user's purchased items in a single query without needing to also look up each item by ID.

Sam Stern
  • 24,624
  • 13
  • 93
  • 124
  • This would work for my question so I will accept it as the answer but in my case I have three ways to purchase items: via app (payedInCoins), via website (payedInMoney) and via Apples in app purchases. These iaps are not saved on my end so I can not just add a name field, I still need to loop through all my items to find the ones with the iapid matching Apples – Casper Zandbergen Oct 20 '17 at 07:50
1

You could combine the Observables using rxjs;

orQuery(){

    const $one = this.afs.collection("items", ref => ref.where("itemId","==","1")).valueChanges();
    const $two = this.afs.collection("items", ref => ref.where("itemId","==","3")).valueChanges();

    return combineLatest($one,$two).pipe(
        map(([one, two]) => [...one, ...two])
    )
}

getOr(){
    this.orQuery().subscribe(data => console.log(data))
}
phicon
  • 3,549
  • 5
  • 32
  • 62