How to filter document by a value in the dictionary in arangodb? - arangodb

I have following structure in arangodb collection named clientShadow.
{
"_class": "com.syncservice.document.ClientShadow",
"sessions": {
"session-id-2": {
"workspaceId": "workspace-id-1",
"message": {
"lastSynced": 0,
"lastAvailable": 1674630705773
}
},
"session-id-1": {
"workspaceId": "workspace-id-1",
"message": {
"lastSynced": 0,
"lastAvailable": 1674630705773
}
},
"session-id-0": {
"workspaceId": "workspace-id-1",
"message": {
"lastSynced": 0,
"lastAvailable": 1674630705773
}
}
},
"synced": true
}
Here sessions is a map/dictionary of session_id as string and session object as value.
I want to fetch all the sessions from collection where session's lastSynced and lastAvailable aren't same.
I tried following query
FOR doc IN clientShadow
FOR session IN doc['sessions']
FILTER session.message.lastSynced != session.message.lastAvailable
RETURN {'session': session}
But I found out that FOR IN works with collections and gives me following error
Query: AQL: collection or array expected as operand to FOR loop; you provided a value of type 'object' (while executing)

To retain the original data and query structure, don't use ATTRIBUTES, use VALUES:
FOR doc IN clientShadow
FOR session IN VALUES(doc['sessions'])
FILTER session.message.lastSynced != session.message.lastAvailable
RETURN {'session': session}
If you cannot change the data structure, you can use ATTRIBUTES to access the sessions as array:
Edit: Code as fixed by Kshiti Kshitij Dhakal (Attributes returns a list of the names of the attributes)
FOR doc IN clientShadow
FOR session IN ATTRIBUTES(doc['sessions'])
FILTER
doc.sessions[session].message.lastSynced
!= doc.sessions[session].message.lastAvailable
RETURN {'session': doc.sessions[session]}
Old (wrong) suggestion:
FOR doc IN clientShadow
FOR session IN ATTRIBUTES(doc['sessions'])
FILTER session.message.lastSynced != session.message.lastAvailable
RETURN {'session': session}
If you can change the data structure, don't use an object for sessions, but a list:
{
"_class": "com.syncservice.document.ClientShadow",
"sessions": [
{
"sessionId": "session-id-2",
"workspaceId": "workspace-id-1",
"message": {
"lastSynced": 0,
"lastAvailable": 1674630705773
}
},
...
]
}

Related

CouchDB Mango query - Match any key with array item

I have the following documents:
{
"_id": "doc1"
"binds": {
"subject": {
"Test1": ["something"]
},
"object": {
"Test2": ["something"]
}
},
},
{
"_id": "doc2"
"binds": {
"subject": {
"Test1": ["something"]
},
"object": {
"Test3": ["something"]
}
},
}
I need a Mango selector that retrieves documents where any field inside binds (subject, object etc) has an object with key equals to any values from an array passed as parameter. That is, if keys of binds contains any values of some array it should returns that document.
For instance, consider the array ["Test2"] my selector should retrieve doc1 since binds["subject"]["Test1"] exists; the array ["Test1"] should retrieve doc1 and doc2 and the array ["Test2", "Test3"] should also retrieve doc1 and doc2.
F.Y.I. I am using Node.js with nano lib to access CouchDB API.
I am providing this answer because the luxury of altering document "schema" is not always an option.
With the given document structure this cannot be done with Mango in any reasonable manner. Yes, it can be done, but only when employing very brittle and inefficient practices.
Mango does not provide an efficient means of querying documents for dynamic properties; it does support searching within property values e.g. arrays1.
Using worst practices, this selector will find docs with binds properties subject and object having properties named Test2 and Test3
{
"selector": {
"$or": [
{
"binds.subject.Test2": {
"$exists": true
}
},
{
"binds.object.Test2": {
"$exists": true
}
},
{
"binds.subject.Test3": {
"$exists": true
}
},
{
"binds.object.Test3": {
"$exists": true
}
}
]
}
}
Yuk.
The problems
The queried property names vary so a Mango index cannot be leveraged (Test37 anyone?)
Because of (1) a full index scan (_all_docs) occurs every query
Requires programmatic generation of the $or clause
Requires a knowledge of the set of property names to query (Test37 anyone?)
The given document structure is a show stopper for a Mango index and query.
This is where map/reduce shines
Consider a view with the map function
function (doc) {
for(var prop in doc.binds) {
if(doc.binds.hasOwnProperty(prop)) {
// prop = subject, object, foo, bar, etc
var obj = doc.binds[prop];
for(var objProp in obj) {
if(obj.hasOwnProperty(objProp)) {
// objProp = Test1, Test2, Test37, Fubar, etc
emit(objProp,prop)
}
}
}
}
}
So the map function creates a view for any docs with a binds property with two nested properties, e.g. binds.subject.Test1, binds.foo.bar.
Given the two documents in the question, this would be the basic view index
id
key
value
doc1
Test1
subject
doc2
Test1
subject
doc1
Test2
object
doc2
Test3
object
And since view queries provide the keys parameter, this query would provide your specific solution using JSON
{
include_docs: true,
reduce: false,
keys: ["Test2","Test3"]
}
Querying that index with cUrl
$ curl -G http://{view endpoint} -d 'include_docs=false' -d
'reduce=false' -d 'keys=["Test2","Test3"]'
would return
{
"total_rows": 4,
"offset": 2,
"rows": [
{
"id": "doc1",
"key": "Test2",
"value": "object"
},
{
"id": "doc2",
"key": "Test3",
"value": "object"
}
]
}
Of course there are options to expand the form and function of such a view by leveraging collation and complex keys, and there's the handy reduce feature.
I've seen commentary that Mango is great for those new to CouchDB due to it's "ease" in creating indexes and the query options, and that map/reduce if for the more seasoned. I believe such comments are well intentioned but misguided; Mango is alluring but has its pitfalls1. Views do require considerable thought, but hey, that's we're supposed to be doing anyway.
1) $elemMatch for example require in memory scanning which can be very costly.

Mongo indexing on nested object

My mongodb collection's structure looke like this,
{
paymentTree: {
t1: { from: "id-123", to: "id-2334", },
t2: { from: "id-1443", to: "id-567", },
t3: { from: "id-76567", to: "id-2334", },
tn: { from: "id-12n", to: "id-233n", }
}
How can index field 'to' in paymentTree?
Currently it is not possible to create index in dynamic object key,
If you want to achieve anyway then I would suggest if you could transform your schema structure to The Attribute Pattern,
change paymentTree object type to array type,
change object to array in key-value pairs, k for tree's dynamic key and v for { from, to } object,
{
paymentTree: [
{
"k": "t1",
"v": { "from": "id-123", "to": "id-2334" }
},
{
"k": "t2",
"v": { "from": "id-1443", "to": "id-567" }
},
{
"k": "t3",
"v": { "from": "id-76567", "to": "id-2334" }
},
{
"k": "tn",
"v": { "from": "id-12n", "to": "id-233n" }
}
]
}
Benefits of this structure:
Create Multikey Single Field Index on to:
You can create multikey indexes on array fields that contain nested objects,
db.collection.createIndex({ "paymentTree.v.to": 1 });
Create Multikey Compound Index on k and to:
db.collection.createIndex({ "paymentTree.k": 1, "paymentTree.v.to": 1 });
Push object to array:
You can insert object to paymentTree array using $push in update methods (updateOne and updateMany).
Pull object from array:
You can remove specific object on the base of k from paymentTree array using $pull in update methods (updateOne and updateMany).
Match conditions:
You can easily match conditions using conditional operators,
Ex.1:
{ "paymentTree.k": "t1" }
Ex.2:
{ "paymentTree.v.to": "id-2334" } // or { "paymentTree.v.from": "id-123" }
Ex.3:
{
"paymentTree": {
"$elemMatch": {
"k": "t1",
"v.to": "id-2334" // or "v.from": "id-123"
}
}
}
How can i easily select like paymentTree.t1, ..:
In Query:
There is a operator called $arrayToObject, you can convert paymentTree array to object in projection stages ($project, $addFields, $set) see Playground,
The find() and findOne() projection can accept aggregation expressions and syntax from MongoDB v4.4 projection consistent with aggregation’s $project stage,
Outside Query:
You need to convert peymentTree array to object format in your client side language (go, node.js, etc.).
this works for me
_, err = db.Comments.Indexes().CreateOne(db.Ctx, mongo.IndexModel{Keys: bson.M{"t1.to": 1}})
if err != nil {
panic(err)
}

Ordering view by document type in couchDB

I have diferent kinds of documents in my couchDB, for example:
{
"_id": "c9f3ebc1-78f4-4dd1-8fc2-ab96f804287c",
"_rev": "7-1e8fcc048237366e24869dadc9ba54f1",
"to_customer": false,
"box_type": {
"id": 9,
"name": "ZF3330"
},
"erp_creation_date": "16/12/2017",
"type": "pallet",
"plantation": {
"id": 62,
"name": "FRF"
},
"pallet_type": {
"id": 2565,
"name": "ZF15324"
},
"creation_date": "16/12/2017",
"article_id": 3,
"updated": "2017/12/16 19:01",
"server_status": {
"in_server": true,
"errors": null,
"modified_in_server": false,
"dirty": false,
"delete_in_server": false
},
"pallet_article": {
"id": 11,
"name": "BLUE"
}
}
So , in all my documents, I have the field : type. In the other hand I have a view that get all the documents whose type is pallet || shipment
this is my view:
function(doc) {
if (doc.completed == true && (doc.type == "shipment" || doc.type == "pallet" )){
emit([doc.type, doc.device_num, doc.num], doc);
}
}
So in this view I get always a list with the view query result, the problem I have is that list is ordering by receiving date(I guess) and I need to order it by document type.
so my question is: How Can I order documents by document.type in a View?
View results are always sorted by key, so your view is sorted by doc.type: first you will get all pallets, then all the shipments. the pallets are sorted by device_num and then num. If you emit several rows with the same keys, the rows are then sorted by _id. You can find more detailed info in the CouchDB documentation.
So your view should actually work the way you want. ;-)

id cannot be used in graphQL where clause?

{
members {
id
lastName
}
}
When I tried to get the data from members table, I can get the following responses.
{ "data": {
"members": [
{
"id": "TWVtYmVyOjE=",
"lastName": "temp"
},
{
"id": "TWVtYmVyOjI=",
"lastName": "temp2"
}
] } }
However, when I tried to update the row with 'id' where clause, the console shows error.
mutation {
updateMembers(
input: {
values: {
email: "testing#test.com"
},
where: {
id: 3
}
}
) {
affectedCount
clientMutationId
}
}
"message": "Unknown column 'NaN' in 'where clause'",
Some results from above confused me.
Why the id returned is not a numeric value? From the db, it is a number.
When I updated the record, can I use numeric id value in where clause?
I am using nodejs, apollo-client and graphql-sequelize-crud
TL;DR: check out my possibly not relay compatible PR here https://github.com/Glavin001/graphql-sequelize-crud/pull/30
Basically, the internal source code is calling the fromGlobalId API from relay-graphql, but passed a primitive value in it (e.g. your 3), causing it to return undefined. Hence I just removed the call from the source code and made a pull request.
P.S. This buggy thing which used my 2 hours to solve failed in build, I think this solution may not be consistent enough.
Please try this
mutation {
updateMembers(
input: {
values: {
email: "testing#test.com"
},
where: {
id: "3"
}
}
) {
affectedCount
clientMutationId
}
}

remove objects from array elastic search

I have required to remove object from array that satisfies the condition, I am able to update the object of array on the basis of condition, which is as follow:
PUT twitter/twit/1
{"list":
[
{
"tweet_id": "1",
"a": "b"
},
{
"tweet_id": "123",
"a": "f"
}
]
}
POST /twitter/twit/1/_update
{"script":"foreach (item :ctx._source.list) {
if item['tweet_id'] == tweet_id) {
item['new_field'] = 'ghi';
}
}",
"params": {tweet_id": 123"}
}
this is working
for remove i am doing this
POST /twitter/twit/1/_update
{ "script": "foreach (item : ctx._source.list) {
if item['tweet_id'] == tweet_id) {
ctx._source.list.remove(item);
}
}",
"params": { tweet_id": "123" }
}
but this is not working and giving this error,
ElasticsearchIllegalArgumentException[failed to execute script];
nested: ConcurrentModificationException; Error:
ElasticsearchIllegalArgumentException[failed to execute script];
nested: ConcurrentModificationException
I am able to remove whole array or whole field using
"script": "ctx._source.remove('list')"
I am also able to remove object from array by specifying all the keys of an object using
"script":"ctx._source.list.remove(tag)",
"params" : {
"tag" : {"tweet_id": "123","a": "f"}
my node module elastic search version is 2.4.2 elastic search server is 1.3.2
You get that because you are trying to modify a list while iterating through it, meaning you want to change a list of object and, at the same time, listing those objects.
You instead need to do this:
POST /twitter/twit/1/_update
{
"script": "item_to_remove = nil; foreach (item : ctx._source.list) { if (item['tweet_id'] == tweet_id) { item_to_remove=item; } } if (item_to_remove != nil) ctx._source.list.remove(item_to_remove);",
"params": {"tweet_id": "123"}
}
If you have more than one item that matches the criteria, use a list instead:
POST /twitter/twit/1/_update
{
"script": "items_to_remove = []; foreach (item : ctx._source.list) { if (item['tweet_id'] == tweet_id) { items_to_remove.add(item); } } foreach (item : items_to_remove) {ctx._source.list.remove(item);}",
"params": {"tweet_id": "123"}
}
For people that need this working in elasticsearch 2.0 and up, the nil and foreach don't get recognized by groovy.
So here's a updated version, including a option to replace a item with the same id by a new object.
and also passing it the upsert will make sure the item gets added even if the document doesn't exist yet
{
"script": "item_to_remove = null; ctx._source.delivery.each { elem -> if (elem.id == item_to_add.id) { item_to_remove=elem; } }; if (item_to_remove != null) ctx._source.delivery.remove(item_to_remove); if (item_to_add.size() > 1) ctx._source.delivery += item_to_add;",
"params": {"item_to_add": {"id": "5", "title": "New item"}},
"upsert": [{"id": "5", "title": "New item"}]
}

Resources