0

What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.

What I need: The array of end users to be used in a .query in the line preceding the snapshot.

Why do I need this: This line is so that the entire database of users is not run on the client.

More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.

JSON of DB

    "people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
  "Coordinates" : {
    "latitude" : -25.809620667034363,
    "longitude" : 28.321706241781342
  },
  "PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
  "caption" : 1602596281762, /// this is timestamp
  "postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
  "e1" : “cvvvv666",
  "e2" : "aol.com",
  "      "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
  "users" : "cvvvv666@aol.com"
},
    

.

var dict = CLLocation()
         ...
dict = CLLocation(latitude: lat, longitude: lon)
        ...
let thisUsersUid = Auth.auth().currentUser?.uid
    //this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
    let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)     
    refArtists2.observe(DataEventType.value,  with: {  snapshot in
        if snapshot.childrenCount>0{
            self.people.removeAll()
            for people in snapshot.children.allObjects as! [DataSnapshot] {
         if people.key != thisUsersUid {
                let peopleObject = people.value as? [String: AnyObject]
                let peopleCoordinates = peopleObject?["Coordinates"] as? String
                let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
                let peoplepostID = peopleObject?["postID"] as? String
                let coordSnap = people.childSnapshot(forPath: "Coordinates")
                guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return  }
                guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return  }
                let locCoord = CLLocation(latitude: lat, longitude: lon)
                let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
                let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
                //let secondsInDay = 86400
                **if Calendar.current.isDateInToday(date)** {
                    let distance = locCoord.distance(from: self.dict)
                    print(distance, "distancexy")
                    **if distance/1609.344 < 3000**{
                        let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
                        self.people.append(peopl)
                        let d = people.key as! String
                        self.printPersonInfo(uid:d)   ///////This is used to reload the data
                    } else {
                        print ("w")
                    }
                } else {
                    print ("alphaaa") 
                }   
            }
            print("aaaaaaaa", self.people.map {$0.distance})    
        } 
        self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }     ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.   
    }      
})
} else {
    print("no")
}
})
                            

Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.

Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?

The timestamp is seconds since 1970

My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).

    var ppp: String!  ////this should be the uid of all users in db

    let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: {  snapshot in
    let captionss = snapshot.value as? Int ?? 0
    let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)       
    let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))
uuuuuu
  • 119
  • 1
  • 7
  • You just need a compound query; `.where timestamp is equal to today` AND `where distance is < 3000 miles`. We don't know how your timetamp is stored but using something simple, `20201102` would be today. As far as distance goes, that requires a [Geohash](https://en.wikipedia.org/wiki/Geohash) and @frankvanpuffelen does a great job of explaining it in his answer to [Query for nearby locations](https://stackoverflow.com/questions/43357990/query-for-nearby-locations). It's a good idea to do some searching here on SO before posting questions as it may have already been answered. – Jay Nov 02 '20 at 20:16
  • Oh and this will be helpful as well [Geofirestore](https://geofirestore.com) – Jay Nov 02 '20 at 20:25
  • Thanks. How would a rough compound query like that look like? I had seen Geohash before I started using Core Location, but for this query I planned to rather use the distance string in the distance calculation. To clarify, the distance calculation gives the distance between logged in user and the others. Couldn't I query via that? – uuuuuu Nov 03 '20 at 09:22
  • The timestamp is seconds since 1970 – uuuuuu Nov 03 '20 at 09:22
  • maybe for caption, something like:`queryOrdered(byChild: "caption") - next line - .queryStarting(atValue: "/BeginningofToday")` – uuuuuu Nov 03 '20 at 10:38
  • I just made an attempt at the caption query. I think it is correct until I got stuck at the `Where` part. Do you agree? – uuuuuu Nov 03 '20 at 15:38

1 Answers1

0

Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.

The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.

Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.

Suppose we have a users collection with user documents - each documentId is the users uid

users
   uid_0
      name: "Leroy"

and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.

posts
   post_id
      postText: "pretty flowers"
      postDate: "20201103"
      postUrl: "www....."
      postUid: "uid_0"
      postTopic: "flowers"

Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.

To do this we will need a compound query and then a subquery to retrieve the posters name as well.

func getTodaysPostsAboutFlowers() {
    let postsCollection = self.db.collection("posts")
    let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
    query.getDocuments(completion: { snapshot, error in
        if let err = error {
             print(err.localizedDescription)
             return
         }

         guard let docs = snapshot?.documents else { return }

         for doc in docs {
            let postText = doc.get("postText") as? String ?? "No text"
            guard let postersUid = doc.get("postUid") as? String else { return }
            self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
         }
    })
}

The above performs a compound query on both the postDate field as the postTopic field.

The above then calls another function to retrieve the users name and output both the name and what they said

func outputPostTextAndUserName(withText: String, andUid: String) {
    let usersCollection = self.db.collection("users")
    let theUserDoc = usersCollection.document(andUid)
    theUserDoc.getDocument(completion: { documentSnapshot, error in
        if let err = error {
             print(err.localizedDescription)
             return
         }

        if let doc = documentSnapshot {
            let postersName = doc.get("name") as? String ?? "No Name"
            print("\(postersName) posted: \(withText)")
        }
    })
}

and the output

Leroy posted: pretty flowers

As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.

Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.

people uid_x timetamps: 9023490823498 //Date(timeIntervalSince1970: todaystamp: "20201106" // yyyymmdd format

that makes querying for nodes that contain today very simple.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks. That makes sense, however, what is unclear to me is how to incorporate `isInToday` in my query. I can't use `isEqualTo` for that one. The specific timestamp that it needs to be greater than is constantly changing as the new day starts. – uuuuuu Nov 03 '20 at 20:00
  • 1
    @uuuuuu If you need to run a query with different parameters then either the query can be user initiated or leverage cloud functions with a cron job to run the query at regular intervals. – Jay Nov 05 '20 at 19:55
  • so it is not possible to run IsInToday as part of a query to Realtime Database before sending users to the Current user's phone (ie you'd need to do that in cloud functions)? That is the crux of my problem, I need a query to check if the timestamp in firebase IsInToday, as well as the distance query <3000 – uuuuuu Nov 06 '20 at 08:58
  • 1
    @uuuuuu Well, I just realized you were using the Realmtime Database, not Firestore. However, you don't do isInToday *before* to get the users - you do isInToday as part of the query in general. You can greatly simplify the whole process by storing a time stamp as one child node (for the hh:mm:ss component) and then 'today' as another. e.g. one child would have `Date(timeIntervalSince1970:` and then another child would have `20200605` so you can then easily query for today; .queryEqual(toValue: "2020605"). – Jay Nov 06 '20 at 17:36
  • That is a fantastic idea. But if I understand correctly, wouldn't it be: queryEqual(toValue: "///ChildThatGivesToday"), ie not the hard coded number? Also, regarding "eg. one child would have Date(timeIntervalSince1970: and then another child would have 20200605" - wouldn't you be comparing 2 different formats: the year:mm:dd and timeIntervalSince1970 format? Lastly, how would one actually store today's date in firebase. You'd use .setValue somewhere I guess – uuuuuu Nov 06 '20 at 20:08
  • @uuuuuu No. Not for comparison. You only need the `(Date(timeIntervalSince1970:`child node if you need to keep track of both the date and time. If you don't care about the time then toss that out completely and stick with storing the `yyyymmdd` format. I am not suggesting hard coding anything - it's there as a placeholder to make the meaning clear. If you have an additional question about how to store or work with dates etc in Firebase, I would suggest doing some searches here on SO and if you dont come up with something, post a separate question. – Jay Nov 06 '20 at 21:23