Query CosmosDb - where array contains item(s) from array - javascript

I don't know if there is a word for this, guess there is, but right now I couldn't explain it better than "where array contains item(s) from array".
It might sound weird, but actually it not (I think), and I'm having a hard time figuring out how I can do this in Azure CosmosDB.
Here goes. I have a document like this (simplified):
{
"id": "2a62fcf4-988f-4ebe-aedc-fb0c664b85d8",
"Title": "Seks års fængsel for overgreb",
"ZipCodes": [
{
"Code": "6500",
"Name": "Vojens",
"FoundViaTerm": "Vojens"
},
{
"Code": "6400",
"Name": "Sønderborg",
"FoundViaTerm": "Sønderborg"
},
{
"Code": "6700",
"Name": "Esbjerg",
"FoundViaTerm": "Esbjerg"
}
],
"_rid": "k1sVAPf7SQAMAAAAAAAAAA==",
"_self": "dbs/k1sVAA==/colls/k1sVAPf7SQA=/docs/k1sVAPf7SQAMAAAAAAAAAA==/",
"_etag": "\"00001000-0000-0000-0000-5a14898e0000\"",
"_attachments": "attachments/",
"_ts": 1511295374
}
Ok, now I want to query documents like this and find all, where ZipCodes.Code is in a list of zipcodes, ex. ('6500', '2700').
I'm puzzle here...
I found the ARRAY_CONTAINS method and it works, if I only come in with one zipcode - my problem is I come with a list.
Hope somebody can help, thanks in advance.

Per my experience , expr in ARRAY_CONTAINS (arr_expr, expr [, bool_expr]) method is not supported list arguments.
According to your situation , I suggest you use UDF in Cosmos DB.
I created 3 sample documents as your description.
[
{
"id": "1",
"zip": [
{
"code": "1111"
},
{
"code": "2222"
}
]
},
{
"id": "2",
"zip": [
{
"code": "2222"
},
{
"code": "3333"
}
]
},
{
"id": "3",
"zip": [
{
"code": "4444"
},
{
"code": "1111"
},
{
"code": "2222"
}
]
}
]
Please refer to the snippet of UDF code as below :
function test(zipcode){
var arrayList = ["1111","2222"]
var ret = false ;
for(var i=0 ;i <zipcode.length;i++){
if(arrayList.indexOf(zipcode[i].code)){
ret= true;
}else{
ret = false;
break;
}
}
return ret;
}
You could select zip array (select c.zip from c) ,then loop the results and invoke the UDF above in your code with the zip[i] arguments.
Hope it helps you.
Just for summary:
Use the IN operator from Cosmos DB SQL APIs to query entry which is included in the list condition.
Like
SELECT * FROM c WHERE c.ZipCodes[0].Code IN ("6500", "6700")
Or
SELECT DISTINCT c FROM c JOIN zc IN c.ZipCodes WHERE zc.Code IN ("2720", "2610")

I would like to propose another solution to this problem.
Use EXISTS with ARRAY_CONTAINS in this way:
SELECT * FROM c
WHERE EXISTS
(SELECT VALUE z FROM z in c.ZipCodes WHERE ARRAY_CONTAINS(["6500","6700"], z))

You can do something like this:
For each item in ZipCodes, you get a zip and compare with the array of codes you are checking. This, IMHO, is much better than using UDF.
{
query: '
SELECT DISTINCT value r
FROM root r
JOIN zip IN r.zipCodes
WHERE ARRAY_CONTAINS(#zipIds, zip, true)
',
parameters: [{name: "#zipIds", value: zipIds}]
}
The last param of ARRAY_CONTAINS tells the function to accept partial matches.

Apart from the fact that using UDF looks as the easier option, i would not use UDFs in your query's filter, since it compromises the performance of your query. I faced the same problem in my work environment, where things are designed to use UDFs to help in the queries, but the reality is that most of the times we are doing queries by using single values, and using UDF will actually result on the query not using the index. So in that case if you need to validate multiple values in the array, depending on the volume of values you need to validate, you can always write something like ARRAY_CONTAINS(c, 1) or ARRAY_CONTAINS(c, 2) or ....
It doesn't look so elegant solution, but will ensure that it will use the index and will do the best performance in your query.

Related

Find specific JSON key and return its value in Javascript

I have some JSON from an API which I am logging in the console.
This is easy for something like the id where I can do this...
let movieID = out.id;
console.log(movieID)
But how can I also return the 'name' of the person whose job is specifically 'Director' from JSON example like this? Basically I need to do something like 'if someone has the JOB of DIRECTOR, log their name to the console.
{
"id": 37291,
"credits": {
"crew": [
{
"name": "John Smith",
"job": "Producer"
},
{
"name": "Mary Jones",
"job": "Director"
}
]
}
}
You can get the list of the crew, like you described above. Then find the first entry in that list where the job equals "Director" and put it in a variable:
let director = out.credits.crew.find(member => member.job == "Director");
You can then log the data, as you did for the movie itself.
Something like this would do literally what you asked which was to console log out the names
credits.crew.foreach((crewMember) => {
if (crewMember.job == 'Director') {
console.log(crewMember.name)
}
})
Use underscore:
_.filter(credits.crew, (key) => key.job === 'Director')
That will give you all the keys with that value. Trust me you should use underscore or lodash because objects can get super tricky and it saves you a ton of time.

How do I use the Bitrix API to filter contacts using multiple values for one key?

I'm trying to return a list of contacts that could match multiple "PHONE" values. Right now I can get a list that matches one phone value but not an array of phone values. Here's what I have:
let contactList = await bitrix.call('crm.contact.list', {
"filter": {
"PHONE": phoneArray, //example ["1112223344","5556651234"]
},
"select": ["*","EMAIL","PHONE"]
});
I'm basing this off their API documentation that shows how to match one phone value here
There's also another article I found that mentions using a "LOGIC":"OR" in a filter that could potentially work. It's written in PHP so I'm not exactly sure how it translates to javascript.
You can use crm.duplicate.findbycomm (https://training.bitrix24.com/rest_help/crm/auxiliary/duplicates/crm.duplicate.findbycomm.php):
BX24.callMethod(
"crm.duplicate.findbycomm",
{
entity_type: "CONTACT",
type: "PHONE",
values: [ "8976543", "11223355" ],
},
function(result)
{
if(result.error())
console.error(result.error());
else
{
console.dir(result.data());
}
}
);
but there are limitations:
An array containing up to 20 e-mails or phone numbers
Maybe it will do use batch (https://training.bitrix24.com/rest_help/js_library/rest/callBatch.php)
Unfortunately crm.contact.list can't match multiple "PHONE" values

Is this the correct use of the $ operator for findOneAndUpdate?

I would like to update a single value in a mongo document that is an array of arrays of arrays,
Here is the code that I have thus far... but it's not working. I think I need to use the $ operator on the chart to access the currently selected chorePerson... but not sure how that works.
I am really struggling with the mongoDB syntax and updating arrays
This is my data set, I have put in a t1, t2 and t3 in each of the respective arrays...
[
{
"_id": "5e3d891d956bb31c307d8146",
"t1": 0,
"affiliation": "800_800",
"year": 2020,
"month": "February",
"weekNumber": 6,
"weekStart": "02/02/2020",
"weekEnd": "02/08/2020",
"chart": [
{
"t2": 0,
"_id": "5e3d8a6358a3d92448e85aa5",
"ordinal": 5,
"ordinalString": "Friday",
"chorePerson": [
{
"completed": false,
"t3": 0,
"_id": "5e3d8a6358a3d92448e85aa7",
"person": "Frank",
"personID": "5e2891587678601434f6929c",
"phone": "8008008002",
"chore": "Another one",
"choreID": "5e39ca3949acee16fc280c98"
}
]
}
],
"date": "2020-02-07T16:03:47.770Z",
"__v": 0
}
]
I have been able to update t1 and t2 using this query:
{"_id":"5e3d891d956bb31c307d8146","chart._id":"5e3d9260fc365c2d080a32ce"}
and a set call of
{"$set":{"t1":1,"chart.$.t2":2}}
but I cannot figure out how to go down one more level in the arrays
Here is my query: I have hard coded the 'chart[ 1 ]' to try to force it to fetch only that record
{"_id":"5e3d891d956bb31c307d8146","chart._id":"5e3d8a6358a3d92448e85aa5","chart[1].chorePerson._id":"5e3d8a6358a3d92448e85aa7"}
This is my 'set' call
{"$set":{"t1":1,"chart.$.t2":2,"chart.$[].chorePerson.$.t3":3}}
When I run this in my program I get:
error Updating the path 'chart.$[].chorePerson.$.t3' would create a conflict at 'chart'
UPDATE 1
So I tried this:
{"_id":"5e3d93777f099b28b0fff2ae","chart._id":"5e3d953ed92a082738e8e2b9","chart.chorePerson._id":"5e3d953ed92a082738e8e2bb"}
{"$set":{"t1":1,"chart.$.t2":2,"chart.$.chorePerson.$.t3":3}}
which gave me:
error Too many positional (i.e. '$') elements found in path 'chart.$.chorePerson.$.t3
So I thought I would go back to the answer posted by whoami:
{"$set":{"t1":1,"chart.$.t2":2,"chart.$[].chorePerson.$.t3":3}}
Which gave me:
error Updating the path 'chart.$[].chorePerson.$.t3' would create a conflict at 'chart'
UPDATE 2
So I tried moving the [] in my set statement to the chart.$.chorePerson
{"$set":{"t1":1,"chart.$.t2":2,"chart.$.chorePerson.$[].t3":3}}
and selecting only the relevant chorePerson (5e3da771e08e3e31ac420004)
{"_id":"5e3da771e08e3e31ac41fffd","chart._id":"5e3da771e08e3e31ac420002","chart.chorePerson._id":"5e3da771e08e3e31ac420004"}
Which is getting me closer... now the data is being set in the correct chore chart and chart, but ALL of the chorePerson fields are being updated even though my select is supposed to only return the chore person '5e3da771e08e3e31ac420004'
After trial and error and lots of stack overflow articles, I have finally found the code that does what I need it to do:
var query = {"chart.chorePerson._id":idCP};
var update = {$set: {"chart.$.chorePerson.$[elem].completed":true, "chart.$.chorePerson.$[elem].completed_at": Date.now()}};
var options = {new: true, arrayFilters: [ { "elem._id": { $eq: idCP } } ]};
if(debugThis){
console.log("query " + JSON.stringify(query));
console.log("update " + JSON.stringify(update));
console.log("options " + JSON.stringify(options));
}
// see stackoverflow question:
// https://stackoverflow.com/questions/60099719/is-this-the-correct-use-of-the-operator-for-findoneandupdate/60120688#60120688
ChoreChart.findOneAndUpdate( query, update, options)
.then(chart => {
if(debugThis){
console.log('updated chart ' + JSON.stringify(chart));
}
return resolve(msgResponse);
})
.catch(err => {
msgResponse.message('error ' + err.message);
return resolve(msgResponse);
})
Which yields exactly what I want:
Many thanks to the people who wrote these articles:
https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndUpdate/#behavior
Update nested subdocuments in MongoDB with arrayFilters
Updating a Nested Array with MongoDB
How to Update Multiple Array Elements in mongodb
arrayFilters in mongodb
https://github.com/Automattic/mongoose/issues/6386

Look if loopback model property array contains a string

I have a Loopback moddel that looks like this:
{
"name": "string",
"elements": [
"string"
]
}
Now I want to filter if elements property conatins a certain string.
Something like this:
User.find({
filter: {
where: {elements: $scope.objects[i].id} //doesn't work, I want sth like "element contains $scope.objects[i].id
}}, function (user) {
console.log(user);
});
Warning: This solution was meant to answer the question "how do I filter a list of objects". It was accepted so I can't remove it. I don't know anything about LoopBack which has performance implications I'm not privy to. So please keep searching if you are looking for a "LoopBack" best practice.
This seems like a javascript question to me. The elements property contains an array so you can filter that array with filter().
yourModel = { // <-- Using a plain object for demo.
"name": "string",
"elements": [
"string"
]
}
matchingElements = yourModel.elements.filter(function(elm){ return elm === $scope.objects[i].id});
didMyModelHaveTheElement = matchingElments.length > 0;

CouchDB view composing JSON objects with embedded arrays from two separated documents

Lets say I have two types of documents stored in my CouchDB database. First is with property type set to contact and second to phone. Contact type document have another property called name. Phone type have properties number and contact_id so that it can reference to contact person. This is trivial one to many scenario where one contact can have N phone numbers (I know that they can be embedded in single contact document, but I need to demonstrate one to many relationship with different documents).
Raw example data with Scott having 2 phone numbers and Matt having 1 number:
{_id: "fc93f785e6bd8c44f14468828b001109", _rev: "1-fdc8d121351b0f5c6d7e288399c7a5b6", type: "phone", number: "123456", contact_id: "fc93f785e6bd8c44f14468828b00099f"}
{_id: "fc93f785e6bd8c44f14468828b000f6a", _rev: "1-b2dd90295693dc395019deec7cbf89c7", type: "phone", number: "465789", contact_id: "fc93f785e6bd8c44f14468828b00099f"}
{_id: "fc93f785e6bd8c44f14468828b00099f", _rev: "1-bd643a6b0e90c997a42d8c04c5c06af6", type: "contact", name: "Scott"}
{_id: "16309fcd03475b9a2924c61d690018e3", _rev: "1-723b7c999111b116c353a4fdab11ddc0", type: "contact", name: "Matt"}
{_id: "16309fcd03475b9a2924c61d69000aef", _rev: "3-67193f1bfa8ed21c68e3d35847e9060a", type: "phone", number: "789456", contact_id: "16309fcd03475b9a2924c61d690018e3"}
Map function:
function(doc) {
if (doc.type == "contact") {
emit([doc._id, 1], doc);
} else if (doc.type == "phone") {
emit([doc.contact_id, 0], doc);
}
}
Reduce function:
function(keys, values) {
var output = {};
for(var elem in values) {
if(values[elem].type == "contact") {
output = {
"ID": values[elem]._id,
"Name": values[elem].name,
"Type": values[elem].type,
"Phones": []
};
} else if (values[elem].type == "phone") {
output.Phones.push({
"Number": values[elem].number,
"Type": values[elem].type
});
}
}
return output;
}
group_level is set to 1 because of keys in Map function. Now I can get my contacts with included phones for example like this:
http://localhost:5984/testdb2/_design/testview/_view/tv1?group_level=1
Or search for some contact with startkey and endkey like this:
http://localhost:5984/testdb2/_design/testview/_view/tv1?group_level=1&startkey=[%22fc93f785e6bd8c44f14468828b00099f%22]&endkey=[%22fc93f785e6bd8c44f14468828b00099f%22,{}]
Results look exactly how I want - contacts will have embedded phones according to one to many relationship. And here goes the question: Is this the right way of how to use MapReduce functions in CouchDB? Are there any notable performance issues when using this approach?
Generally speaking you use less disk space if you do not emit(...,doc).
You may want to reconsider having a reduce function at all. It's really not necessary to get at the data you need. For example, something along the lines of the following may use less disk space and perform better if you have a huge number of records.
Also, I believe it is against the grain of CouchDB to build up more data in a reduce function than your documents contain. You're not doing that in this case but you are following a pattern that might lead you into trouble later. It's called reduce for a reason. :-)
So something like this is more the CouchDB way:
function(doc) {
if (doc.type == "contact") {
emit([doc._id, 0], {
"Name": doc.name,
"Type": doc.type
});
} else if (doc.type == "phone") {
emit([doc.contact_id, 1], {
"Number": doc.number,
"Type": doc.type
});
}
}
Query it for a particular contact like so:
http://localhost:5984/testdb2/_design/testview/_view/tv1?
startkey=[%22fc93f785e6bd8c44f14468828b00099f%22, 0]
&endkey=[%22fc93f785e6bd8c44f14468828b00099f%22,1]
Granted, you don't get results in the same JSON structure as before but I believe this performs better within CouchDB.
This answer is completely apocryphal and anecdotal, but that's pretty much exactly how I've worked with one-to-many relationships in CouchDB. If there are any scaling issues, I haven't seen them yet. (But I admit I haven't tried too hard to find them.)
Although, in your map function why do you have your Phone sorted to come out first (0) before the Contact (1)? Your reduce function requires the opposite order.

Categories

Resources