Firebase - Querying in flattened data with Two Way Relationship - firebase

I currently have a Firebase database with the following structure
// Tracking two-way relationships between users and groups {
" users": {
"mchen": {
"name": "Mary Chen",
"groups": {
"alpha": true,
"charlie": true
}
},
...
}, "groups": {
"alpha": {
"name": "Alpha Group",
"members": {
"mchen": true,
"donald": true
}
}, "bravo": {
"name": "Bravo Group",
"members": {
"mickey": true,
"donald": true
}
},
...
}
}
How do I write a query to show me all the groups a given set of users have in common. i.e. show me all groups where Mickey and Donald both registered.

I don't think that is possible with a single query, and multiple equalTo are not supported.
I would restructure your database as such
root
L users
L groups
L groupName
L ...
L groupMembers
L groupName
L userName:true
Then query like
Query is reference.child('groupMembers').orderByChild('Donald').equalTo(true);
Then go through the results
List resultList
for all DataSnapshot's as snapshot in dataSnapshot's Children
if snapshot.child('Mickey') is not null
add to resultList key of snapshot
// resultList now contains all group keys/names which Donald and Mickey are both appart of.
That would be one way to solve your problem, but you would initially be downloading groups which Mickey might not be apart of. this may or may not be what you want (security etc..).
If you wanted to only get groups which they are both appart of without any clientside filtering, you would have to restructure your database to something like this.
root
L users ...
L groups ...
L groupPairs
L groupName
L DonaldMickey:true
Your query would look similar, adding/removing a user from a group would be more elaborate. You would have to make sure that every possible pair is under the groupName. You could reduce the amount of pairs by setting a criteria like for example: Donald before Mickey because D is before M, or something like that.

Related

firebase realtime database query to find data

I wish to store data for some children activities where each activity is good for certain age range. Let's say act A is good for 2 - 5 year old. act B is good for 0 -1 year old.
On the client side, there is a fixed set of choices like:
0 - 1 years,
1 - 3 years,
4 - 5 years,
6 - 13 years
Now the requirement is that the activity A should come up for selection 1 - 3 as well as 4 -5 years as 2 - 5 overlaps both the ranges.
What would be the good way to store activity data and then query it efficiently ?
Assuming the fixed set of choices is a permanent feature to your application, I'd have a boolean field for each match, for example, your activities would look like:
activities: {
activityA: {
range0to1: false,
range2to3: true,
range4to5: true,
range6to13: false
},
activityB: {
range0to1: true,
range2to3: false,
range4to5: false,
range6to13: false
}
}
And then when you want to query all activities which apply for eg. ages 2 to 3, then you already have the field to query with nothing too complicated.
But really for longevity, I wouldn't assume that the fixed set of choices is permanent for the lifetime of a an app, in which case I'd rather have something like:
activities: {
activityA: {
minAge: 2,
maxAge: 5,
},
activityB: {
minAge: 0,
maxAge: 1,
}
}
...and then if I want to query for the fixed choice of ages between x and y, my ideal query would be for all activities where either minAge or maxAge are between x and y (hence there's an overlap in the range)
eg (pseudocode) where ((minAge > x and minAge < y) or (maxAge > x or maxAge < y))
But unfortunately, in practice, firebase RTDB doesn't let you query by multiple fields, so if it's not too late, I'd recommend looking at Firestore which may be better suited for your needs (personally I think I'd typically recommend firestore over RTDB for most use-cases).
If you are stuck with RTDB, then another solution might be to create a lookup block at the root of your structure:
{
activities: {
activityA: {
// age range of 2-5 stored however you like
},
activityB: {
// age range of 0-1 stored however you like
},
activityC: {
// age range of 0-3 stored however you like
}
},
ageActivityLookup: {
age0: {
activityB: true,
activityC: true,
},
age1: {
activityB: true,
activityC: true,
},
age2: {
activityA: true,
activityC: true,
},
age3: {
activityA: true,
activityC: true,
},
age4: {
activityA: true,
},
age5: {
activityA: true,
}
}
}
So then you can simply query ageX and get your list of activities. This will mean multiple queries if you're looking for a range of ages, and does mean having to ensure your lookup block stays in sync. This should be OK if the rest of your application data structure isn't too complex.
#hussein as an inspiration from your idea i simplified it a bit to adjust to my usecase. And instead of a separate node i actually added each age group classification within the activity like:
baby:true
teen:true
and so on.
This saves from overhead of maintaining and updating an entire node with increasing complexity asactivities grow

RethinkDB filtering Object Array

I'm new to rethinkdb and I wanted to filter something like... get all with Kiwi or Strawberry as preferred fruit
{
"id": "65dbaa34-f7d5-4a25-b01f-682032fc6e05" ,
"fruits": {
"favorite": "Mango" ,
"preferred": [
"Kiwi" ,
"Watermelon"
]
}
}
I tried something like this after reading contains doc:
r.db('appname').table('food')
.filter(r.row('fruits').contains(function(doc) {
return doc('preferred').contains('Kiwi');
}))
And I'm getting a e: Cannot convert OBJECT to SEQUENCE in: error.
This is what you're looking for:
r.db('appname').table('food')
.filter((row) => {
r.or( // Returns true if any of the following are true
row('fruits')('preferred').contains('Kiwi'),
row('fruits')('preferred').contains('Strawberry')
)
});
You should know as well, that you can create your own index that calculates this for you, then you'd be able to do a .getAll query using your custom index and return all documents that fit this constraint very quickly.
Lastly, for something that would also work but is probably less efficient on large arrays:
r.db("appname").table('food')
.filter((row) => {
return row('fruits')('preferred').setIntersection(['Kiwi', 'Strawberry']).count().gt(0)
})

How do I configure OrientDB ETL to import an edge list with attributes

I have an CSV which contains an edge list, one edge per row. It looks like this:
id1, id2, attr1, attr2, attrX, attrY, attrZ
From this, I want to be able to create (or update) the following, per row:
Vertex A of class X, with id1 and attribute attr1
Vertex B of class X, with id2 and attribute attr2
Edge A->B with edge attributes attrX, attrY, attrZ
This is the configuration file I'm feeding to oetl.sh (using OrientDB 2.2 beta2):
{
"source": { "file": { "path": "/data/sample/test.csv" } },
"extractor": { "row": {} },
"transformers" :
[
{ "csv" : {} },
{ "merge" : { "joinFieldName":"id1", "lookup":"X.id" } },
{ "vertex" : { "class" : "X", "skipDuplicates":true } },
{ "edge" : {
"unresolvedLinkAction" : "WARNING",
"class" : "EdgeTypeClass",
"joinFieldName" : "id2",
"lookup": "X.id",
"edgeFields":{"attrX":"${input.attrX}", "attrY":"${input.attrY}","attrZ":"${input.attrZ}"}
}
},
{ "field" : { "fieldNames" : [ "id1", "id2", "attr1", "attr2", "attrX", "attrY", "attrZ" ], "operation": "remove" } }
],
"loader": {
"orientdb": {
"dbURL": "remote:localhost/test2",
"dbType": "graph"
}
}
}
The sample data I used to run the test is as follows:
10,11,"A","B",100,200,1
11,12,"B","C",110,201,5
12,14,"C","D",90,250,10
14,13,"D","E",105,210,3
When I run the oetl.sh script with the given configuration and sample data, it creates 4 vertices instead of 5 and no edges. There are no attributes on the vertices at all.
So these are the questions:
Is there a way in the vertex clause to specify vertex attributes/fields the same way that one can do for edges (i.e. edgeFields)? The documentation doesn't mention anything about it but it seems odd that you wouldn't be able to do it.
Rather than relying on the edge to create the outbound vertex, should I instead be creating two vertices explicitly and if so how do I specify that in the configuration file? When I try to add two "vertex" clauses it only seems to pick up the last one as the "current" vertex.
It's possible that the specific edge (id1 -> id2) already exists. Is it possible to only update the edge attributes in this case?
My sinking feeling is that given the complexity and number of things I'm trying to pack into this that it will be simpler to write my own ETL (e.g. using the Java API) instead of relying on oetl, but I was hoping I'd be able to avoid doing that if only because it's more maintainable.

Why does DynamoDB simple deleteItem operation use 2 CapacityUnits?

I have a simple delete operation which goes like this:
{
"TableName":"demo_events",
"Key":{
"category":{"S":"Demo"},
"DynamoID":{"S":"164933868Slt1396454204"}
},
"Expected":{
"category":{
"Exists":true,
"Value"{"S":"Demo"}
}
},
"ReturnConsumedCapacity":"TOTAL",
"ReturnItemCollectionMetrics":"SIZE"}
There is only a single item in database with that ID. The response is this:
{
ConsumedCapacity: {
CapacityUnits: 2,
TableName: 'demo_events'
},
ItemCollectionMetrics: {
ItemCollectionKey: {
category: { S: 'Demo' }
},
SizeEstimateRangeGB: [ 0, 1 ] }
}
Shouldn't this only consume 1 write unit?
Many thanks.
For PutItem, UpdateItem, and DeleteItem, which write only one item, DynamoDB rounds the item size up to the next 1 KB. If you have other attributes in the item in addition to the key attributes, they all together could add up to more than 1 KB.
If there is a Local Secondary Index (LSI) on the table, DeleteItem would also delete the corresponding item from the LSI and item size would contribute to the total Write Capacity Units consumed. DeleteItem response returns an ItemCollectionMetrics when there is a LSI defined for the table. There seems to be a LSI defined for the table based on the sample response
regards

Associating child to it's parent's parent?

In OpenERP I have 3 models, A, B and C. If you start on the form for A, there is a tree list of children B. when you click on one of those B children that form has a tree list of children C. I need the C children to be associated to both parents A and B but they will only associate themselves with B despite having many2one fields for both A and B. How can I force this association?
I have looked at solutions using active_id and default_get without success. The context object seems useful for this but I see no way of dynamically setting it with ids for both parent objects as I drill down from A to C navigating through the forms. I don't understand why context is not normally used for holding the context in this manner like in various web frameworks.
To clarify, when I edit object A and from it's edit form create an instance of B and from B's edit form create an instance of C how do I make the C associated to A and B?
Should I just execute a simple update with subselect query in C's create method to make this happen or will that break the ORM?
Looking at the transaction data when I save a new C instance (document) I see that everything I need in terms of ids is within the transaction but I don't know how to access and manipulate the values as I need to e.g.
{
"jsonrpc":"2.0",
"method":"call",
"params":{
"model":"dbe.vendor",
"method":"write",
"args":[
[
3
],
{
"application":[
[
4,
2,
false
],
[
1,
21,
{
"documents":[
[
4,
37,
false
],
[
4,
35,
false
],
[
4,
46,
false
],
[
4,
36,
false
],
[
0,
false,
{
"state":"new",
"name":"order of precendence test",
"description":"TESTING",
"type_of":7,
"locked":false,
"note":false,
"datas":false,
"datas_fname":false,
"type":"binary",
"application_id":false,
"certification_id":false,
"vendor_id":3,
"message_follower_ids":false,
"message_ids":false
}
]
]
}
]
]
}
],
"kwargs":{
"context":{
"lang":"en_US",
"tz":"EST",
"uid":7
}
},
"session_id":"303ae4c1bd9d49079c4efc9e06e0184f",
"context":{
"lang":"en_US",
"tz":"EST",
"uid":7
}
},
"id":"r138"
}
NOTE: I manually inserted vendor_id because it is a required field but it is the field I want to automatically populated.
Perhaps I am not literate enough with OpenERP to make this association happen properly or there isn't enough relevant documentation or the ORM isn't designed for this use (or any combinations of these) but I need this to work as per my requirements and now it does. I apologize if it is offensive in any manner. Criticisms and advice always welcome.
def create(self, cr, uid, vals, context=None):
"""Creates a new dbe.document instance. Called by both internal app and client.
documents are associated to dbe.vendor, dbe.application and dbe.certification.
#param vals: All dbe.document field values as a dictionary.
#return ID of new dbe.document instance.
"""
if context is None:
context={}
doc_id = None
association_id = None
# vendor_id is passed with vals when documents are created from the client side.
new_vendor_id = vals.get('vendor_id',False)
if new_vendor_id:
doc_id = super(dbe_document,self).create(cr, uid, vals, context=context)
_logger.debug("<CREATE> DBE Document (%d) created for vendor #%d by user %d", doc_id, new_vendor_id, uid)
else: # within OpenERP we cannot get vendor_id from context so we do it caveman style.
doc_id = super(dbe_document,self).create(cr, uid, vals, context=context)
association_id = self.read(cr, uid, doc_id, ['application_id', 'certification_id'], context=context)
if association_id['application_id']:
application_id = association_id['application_id']
application_obj = self.pool.get('dbe.application')
application = application_obj.browse(cr, uid, application_id)
new_vendor_id = application.vendor_id.id
elif association_id['certification_id']: # since there is no application maybe its a certification related doc....
certification_id = association_id['certification_id'][0]
certification_obj = self.pool.get('dbe.certification')
certification = certification_obj.browse(cr, uid, certification_id)
new_vendor_id = certification.vendor_id.id
else:
raise osv.except_osv(_('ValidateError'), _('<CREATE> A DBE Document cannot be created without an application or certification - Association Missing!'))
if new_vendor_id: # brute-force association to dbe.vendor.
vals.update({'vendor_id': new_vendor_id})
self.write(cr, uid, doc_id, vals, context)
_logger.debug("<CREATE> DBE Document (%d) created for vendor #%d by user %d", doc_id, new_vendor_id, uid)
else: # too bad - no vendor, no doc.
_logger.debug("<CREATE> DBE Document (%d) removed because vendor_id missing for user %d", doc_id, uid)
raise osv.except_osv(_('ValidateError'), _('<CREATE> A DBE Document cannot be created without selecting a Vendor - Vendor Id Missing!'))
return doc_id
better to use fields.related to make such association where you can do refer "A" from "B" in form of "C".

Resources