I have multiple posts and each post have it's own comments. I want to query all posts, ordered by their time stamps. I tried:
let query = Firebase(url: path)
let query.queryOrderedByChild("commentDate").observeEventType(.ChildAdded)
{ (queryResponse, cancelBlock) { ....
"commentDate" are integers on server. At this query I get only the first object
let query = Firebase(url: path)
query.queryOrderedByChild("commentDate").queryLimitedToFirst(100).observeEventType(.ChildAdded) { (queryResponse, cancelBlock) { ...
I get only the first object as well...
The single solution I have at the moment is to query all :
query.queryOrderedByChild("commentDate").observeEventType(.Value) { (queryResponse, cancelBlock) in ...
But using this I must sort the array before showing to user.
Any ideas how can I do to get them sorted on server?
Edit
Representation of firebase data:
{
"-KGRZmSIDXz5hHkelthQ" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGRa5skGzA1GAG09Lno" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGVr-tti1zr1M1QLlHL" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : {
"-KGX1_rFSBLmQJ7QzCRc" : {
"commentBody" : "something",
"commentDate" : 1.461933727259896E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"commentBody" : "something",
"commentDate" : 1.461933797884702E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGXVGKA0SYm-vRs6zsv" : {
"commentBody" : "something",
"commentDate" : 1.461941507496439E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
},
}
}
Firebase can query nested children if you know the entire path (so something like dimensions/length). It cannot handle dynamic elements in that path under each child (like your $commentid/commentDate).
The only way to get this working is to pull the necessary data up to a level where you can query it. For example, if you keep track of the lastCommentDate for each blog post:
{
"-KGRZmSIDXz5hHkelthQ" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGRa5skGzA1GAG09Lno" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGVr-tti1zr1M1QLlHL" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"firstCommentDate": 1.461933727259896E9,
"lastCommentDate": 1.461941507496439E9,
"comments" : {
"-KGX1_rFSBLmQJ7QzCRc" : {
"commentBody" : "something",
"commentDate" : 1.461933727259896E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"commentBody" : "something",
"commentDate" : 1.461933797884702E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGXVGKA0SYm-vRs6zsv" : {
"commentBody" : "something",
"commentDate" : 1.461941507496439E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
},
}
}
With this structure you can query blog posts by their last comment date:
ref.orderByChild('lastCommentDate').limitToLast(10).on(...
Your data structure looks fine, so a simple query will return the results you want
let commentsRef = ref.childByAppendingPath("-KGVr-tti1zr1M1QLlHL/comments")
commentsRef.queryOrderedByChild("commentDate").observeEventType(.ChildAdded, withBlock: {
snapshot in
let nodeData = snapshot.value
print(nodeData!)
})
The above will return each child node, one at a time in the proper order.
If you have a small amount of nodes, you can just read them in at one time and iterate over the snapshot.children like this
usersRef.queryOrderedByChild("commentDate").observeEventType(.Value, withBlock: {
snapshot in
for child in snapshot.children {
print(child)
}
})
Both of the above will print out each comment node ordered by commentDate
The above will work only if you are interested in retrieving the comments within each post. If not, you'll need to change your Firebase structure and move the comments to separate node, like this
posts
post_01
post_02
etc
and a separate comments node that's refers to the post the comment belongs to. Since you want to query for say the comments for post_02, and want them ordered by commentDate, combine the post number and date into a single child.
comments
"-KGX1_rFSBLmQJ7QzCRc" : {
"for_post_and_commentDate": "post_02_1.461933727259896E9",
"commentBody" : "something",
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"for_post_and_commentDate": "post_02_1.461933797884702E9",
"commentBody" : "something",
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
and then modify the query
usersRef.queryOrderedByChild("for_post_and_commentDate")
queryStartingAtValue("post_02_0").queryEndingAtValue("post_02_xxx").observeEventType(.Value, withBlock: {
You'll have to figure out your commentDate format as it would be easier to craft starting and ending timestamps if they were stored: 20160430100621 as a yyyymmddhhmmss format, then your timestamps could be queried like this:
starting: post_02_0
ending: post_02_99999999999999
This also gives you the flexibility to query for comments for post_02 that happened yesterday, or last week.
Related
How can i filter an array of json in swift having only certain key value pairs?
my array looks like:
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd",
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc",
}]
i want to get an array of json output having only status, chapter & correct key value pairs.
Like:
[{
"status" : "true",
"correct" : "3",
"chapter" : "34",
}, {
"status" : "true",
"correct" : "2",
"chapter" : "35",
}]
Considering this is your JSON
var myJSON = """
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc"
}]
"""
Simply create a Decodable struct like this
typealias MyArray = [MyObject] // Use this to decode
struct MyObject: Codable {
let status, correct, chapter: String
}
And use it like this
//Usage
var myJSONData = myJSON.data(using: .utf8)! // converting the JSON to data
let objArray = try! JSONDecoder().decode(MyArray.self, from: myJSONData) // decoding the json data into an object
//how to access
print(objArray.count)// number of elements in my array
print(objArray.first!) // getting the first object
let myObject = obj[0] // also getting the first object by index
myObject.chapter
myObject.correct
myObject.status
Read about Codable here .
If it's already in an object, you can try
filteredArray = myArray.map { ["status": $0.status, "correct": $0.correct, "chapter": $0.chapter] }
You can try
do {
let res = try JSONDecoder().decode([Root].self, from:data)
}
catch {
print(error)
}
struct Root: Codable {
let status, correct, chapter: String
}
Correct json
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc"
}]
It's more suitable to make
status a bool not string
correct & chapter to be integers
so json look like
[{
"status" : true,
"score" : "3",
"correct" : 3,
"chapter" : 34,
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : true,
"score" : "0",
"correct" : 2,
"chapter" : 35,
"answer" : "35854",
"solutionText" : "abc"
}]
you can also make other values like that if you need , then your model will look like
struct Root: Codable {
let status: Bool
let correct, chapter: Int
}
Edit:
let data = try content.rawData() // this inside do block
where content is of type JSON
try Decodable of swift
class MyObjectsClass : Decodable {
var objects : [Objects]?
}
class Objects : Decodable {
var status : String?
var correct : String?
var chapter : String?
}
While Decoding
let decodedValue = try? JSONDecoder.decode(MyObjectsClass.self, from : data)
// where data from API calls
How could I query this data in this way:
I would like to ignore the CurrentSubAdministrativeArea child and iterate every each sub child and find the right userKey
Actually I'm using this code, that isnt working:
self.ref.child("Ads").child("CurrentSubAdministrativeArea")
/*HERE I would like to ignore the childs*/
.queryOrdered(byChild: "userKey").queryEqual(toValue: uid).observeSingleEvent(of:.value, with: { (snapshot) in
--
{
"Ads" : {
"CurrentSubAdministrativeArea" : {
"Mantova" : {
"-L7ymBmmbHkNfhBRte9F" : {
"cost" : 200,
"date" : 1527256922000,
"info" : "Test",
"maxLimit" : 100,
"minLimit" : 10,
"personBadType" : [ "abitudinaria", "antipatica" ],
"personGoodType" : [ "simpatica", "felice" ],
"subAdministrativeArea" : "Mantova",
"title" : "Mantova Test",
"url" : "https://firebasestorage.googleapis.com/v0/b/team-34540.appspot.com/o/Mantova%20Test?alt=media&token=3a81ed1c-ecd6-4dc0-bd7c-45e093ce8188",
"userKey" : "OsJRc98sqxPx70iqxFtoqerMzHH2",
"via" : "viale dei test"
}
},
"Milano" : {
"-L6qywMC6nxi0fJNMHba" : {
"cost" : 454,
"date" : 1528298580000,
"info" : "Di pollo",
"maxLimit" : 100,
"minLimit" : 10,
"personBadType" : [ "abitudinaria", "antipatica" ],
"personGoodType" : [ "simpatica", "felice" ],
"subAdministrativeArea" : "Milano",
"title" : "Pollo 2",
"url" : "https://firebasestorage.googleapis.com/v0/b/team-34540.appspot.com/o/Pollo?alt=media&token=fc6a3ec8-5f9a-4347-bdad-2d9715af784d",
"userKey" : "OsJRc98sqxPx70iqxFtoqerMzHH2",
"via" : "viale test"
}
}
}
}
}
You could denormalize your data in such a way your query is easy to build and execute.
Together with the data structure you already have you would have another node (ie. another data structure) like
{
"AdsByUsers" : {
"OsJRc98sqxPx70iqxFtoqerMzHH2": {
"Mantova",
"Milano",
...
},
"abcde88qxPx70iqxFtoqerMzKh5": {
"Firenze",
...
}
With NoSQL database you should not hesitate to duplicate data in such a way your queries are easy and fast to execute.
I have this database on Firebase:
{
"issues" : {
"-L04771_EjrLlv5u1-GU" : {
"issue" : "Test insert 1",
"last_edit" : "d8QICgTG5xR20RBzAXfzfu8gLgw2",
"owner" : "d8QICgTG5xR20RBzAXfzfu8gLgw2",
"owner_email" : "example1#gmail.com",
"status" : 1,
"url" : "http://www.example.com/example.html"
},
"-L047pIoqxkj4saaTYyQ" : {
"issue" : "Test insert 2",
"last_edit" : "d8QICgTG5xR20RBzAXfzfu8gLgw2",
"owner" : "d8QICgTG5xR20RBzAXfzfu8gLgw2",
"owner_email" : "example2#gmail.com",
"status" : 1,
"url" : "http://www.example.com/example.html"
}
}
}
I have to extract only those who have owner_email "example1#gmail.com".
Is possible?
You're probably looking for Firebase Queries and especially the equalTo-Query.
Your code would be something like this:
// Find all issues with owner_email = example1#gmail.com
var ref = firebase.database().ref("issues");
ref.orderByChild("owner_email").equalTo("example1#gmail.com").on("value", function(snapshot) {
// Loops through the matching issues
snapshot.forEach(function(child) {
console.log(child.key);
});
});
I'm trying to do an inner join on a FIRDatabaseQuery object.
below is the database structure. I have some posts that are linked to post-comments. I am trying to get all the posts that a specific user added a comment on:
{
"posts" : {
"-KIycKhZU55dmKnHbbxv" : {
"author" : "John Doe",
"body" : "This is a post test",
"title" : "test",
"uid" : "SHaH36BLwgPvwgi9cDmRnsnONFB2"
},
"-KIyg_ks1O5QL4_4dfq_" : {
"author" : "Jane Doe",
"body" : "This is a post test",
"title" : "test2",
"uid" : "x5leSBGArnd10JilD9YDyNBNfZ03"
},...
}
"post-comments" : {
"-KIycKhZU55dmKnHbbxv" : {
"-KIycMyL0Vy1BHVdI4zc" : {
"author" : "toto",
"text" : "test",
"uid" : "SHaH36BLwgPvwgi9cDmRnsnONFB2"
},
"-KIyg_ks1O5QL4_4dfq_" : {
"author" : "toto",
"text" : "test",
"uid" : "SHaH36BLwgPvwgi9cDmRnsnONFB2"
}
},...
}
in SQL this will be translated into a inner join query similar to:
Select * from post inner join post-comments on post-id where post-comments.uid = "user id"
Does anybody know how o get something similar to an inner join in firebase?
Thanks a lot,
Yacine
You may want to consider
"posts" : {
"-KIycKhZU55dmKnHbbxv" : {
"author" : "John Doe",
"body" : "This is a post test",
"title" : "test",
"uid" : "SHaH36BLwgPvwgi9cDmRnsnONFB2"
"commented_by"
"SHaH36BLwgPvwgi9cDmRnsnONFB2": true
Then you can simply query for
posts.child("commented_by/uid").queryEqualToValue(true)
or, change the post-comments around to better match the queries you want to do:
"post-comments" : {
"-KIycMyL0Vy1BHVdI4zc" : {
"author" : "toto",
"post_data":
post_id: "-KIycKhZU55dmKnHbbxv",
post_title: "test"
"text" : "test",
"uid" : "SHaH36BLwgPvwgi9cDmRnsnONFB2"
},
That makes the query a snap as the post-comments node can be queried for the uid which return all of the post_id's of the posts they commented on. It wouldn't return the post itself, however, you may just be looking for the title so you can populate a list. Once the user taps it clicks it you can retrieve the actual data.
You will need to work with nested firebase calls. You can find a javascript example in this question but your swift code should look like the following:
ref.child("posts").observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let postId = snapshot.key as! String {
let commentsRef = ref.child("post-comments")
commentsRef.child(postId).queryOrderedByChild("uid").queryEqualToValue(userId).observeSingleEventOfType(.Value, withBlock: { (snapshot) in
for child in snapshot.children.allObjects as [FDataSnapshot] {
print(child.value)
}
}) { (error) in
print(error.localizedDescription)
}
}
})
Consider the following example of mongo collection:
{"_id" : ObjectId("4f304818884672067f000001"), "hash" : {"call_id" : "1234"}, "something" : "AAA"}
{"_id" : ObjectId("4f304818884672067f000002"), "hash" : {"call_id" : "1234"}, "something" : "BBB"}
{"_id" : ObjectId("4f304818884672067f000003"), "hash" : {"call_id" : "1234"}, "something" : "CCC"}
{"_id" : ObjectId("4f304818884672067f000004"), "hash" : {"call_id" : "5555"}, "something" : "DDD"}
{"_id" : ObjectId("4f304818884672067f000005"), "hash" : {"call_id" : "5555"}, "something" : "CCC"}
I would like to query this collection and get only the first entry for each "call_id", in other words i'm trying to get unique results based on "call_id".
I tried to use .distinct method:
#result = Myobject.all.distinct('hash.call_id')
but the resulting array will contain only the unique call_id fields:
["1234", "5555"]
and I need all the other fields too.
Is it possible to make a query like this one?:
#result = Myobject.where('hash.call_id' => Myobject.all.distinct('hash.call_id'))
Thanks
You cannot simply return the document(or subset) by using the distinct. As per the documentation it only returns the distinct array of values based on the given key. But you can achieve this by using map-reduce
var _map = function () {
emit(this.hash.call_id, {doc:this});
}
var _reduce = function (key, values) {
var ret = {doc:[]};
var doc = {};
values.forEach(function (value) {
if (!doc[value.doc.hash.call_id]) {
ret.doc.push(value.doc);
doc[value.doc.hash.call_id] = true; //make the doc seen, so it will be picked only once
}
});
return ret;
}
The above code is self explanatory, on map function i am grouping it by key hash.call_id and returning the whole doc so it can be processed by reduce funcition.
On reduce function, just loop through the grouped result set and pick only one item from the grouped set (among the multiple duplicate key values - distinct simulation).
Finally create some test data
> db.disTest.insert({hash:{call_id:"1234"},something:"AAA"})
> db.disTest.insert({hash:{call_id:"1234"},something:"BBB"})
> db.disTest.insert({hash:{call_id:"1234"},something:"CCC"})
> db.disTest.insert({hash:{call_id:"5555"},something:"DDD"})
> db.disTest.insert({hash:{call_id:"5555"},something:"EEE"})
> db.disTest.find()
{ "_id" : ObjectId("4f30a27c4d203c27d8f4c584"), "hash" : { "call_id" : "1234" }, "something" : "AAA" }
{ "_id" : ObjectId("4f30a2844d203c27d8f4c585"), "hash" : { "call_id" : "1234" }, "something" : "BBB" }
{ "_id" : ObjectId("4f30a2894d203c27d8f4c586"), "hash" : { "call_id" : "1234" }, "something" : "CCC" }
{ "_id" : ObjectId("4f30a2944d203c27d8f4c587"), "hash" : { "call_id" : "5555" }, "something" : "DDD" }
{ "_id" : ObjectId("4f30a2994d203c27d8f4c588"), "hash" : { "call_id" : "5555" }, "something" : "EEE" }
and running this map reduce
> db.disTest.mapReduce(_map,_reduce, {out: { inline : 1}})
{
"results" : [
{
"_id" : "1234",
"value" : {
"doc" : [
{
"_id" : ObjectId("4f30a27c4d203c27d8f4c584"),
"hash" : {
"call_id" : "1234"
},
"something" : "AAA"
}
]
}
},
{
"_id" : "5555",
"value" : {
"doc" : [
{
"_id" : ObjectId("4f30a2944d203c27d8f4c587"),
"hash" : {
"call_id" : "5555"
},
"something" : "DDD"
}
]
}
}
],
"timeMillis" : 2,
"counts" : {
"input" : 5,
"emit" : 5,
"reduce" : 2,
"output" : 2
},
"ok" : 1,
}
You get the first document of the distinct set. You can do the same in mongoid by first stringify the map/reduce functions and call mapreduce like this
MyObject.collection.mapreduce(_map,_reduce,{:out => {:inline => 1},:raw=>true })
Hope it helps