If I need to filter by array of IDs, how would I do that using bindings?
Documentation does not provide any hints on that.
for c in commit
filter c.hash in ['b0a3', '9f0eb', 'f037a0']
return c
Updating the answer to deal with bindings reference that I missed.
LET commit = [
{ name: "111", hash: "b0a3" },
{ name: "222", hash: "9f0eb" },
{ name: "333", hash: "asdf" },
{ name: "444", hash: "qwer" },
{ name: "555", hash: "f037a0" }
]
FOR c IN commit
FILTER c.hash IN #hashes
RETURN c
The key is that when you send the bind param #hashes, it needs to be an array, not a string that contains an array.
If you use the AQL Query tool via the ArangoDB Admin Tool, make sure you click the "JSON" button in the top right to ensure the parameter hashes has the value
["b0a3", "9f0eb", "f037a0"] and not
"['b0a3', '9f0eb', 'f037a0']"
If you want to send a string as a parameter such as "b0a3","9f0eb","f037a0", so { "hashes": "\"b0a3\",\"9f0eb\",\"f037a0\"" } as bind parameter, then you can split the string into an array like this:
LET commit = [
{ name: "111", hash: "b0a3" },
{ name: "222", hash: "9f0eb" },
{ name: "333", hash: "asdf" },
{ name: "444", hash: "qwer" },
{ name: "555", hash: "f037a0" }
]
FOR c IN commit
FILTER c.hash IN REMOVE_VALUE(SPLIT(#hashes, ['","', '"']), "")
RETURN c
This example will take the string #hashes and then SPLIT the contents using "," and " as delimiters. This converts the input variable into an array and then the query works as expected. It will also hit an index on the hash attribute.
The delimiters are enclosed with single quote marks to avoid escaping, which would also be possible but less readable: ["\",\"", "\""]
Note that "," is listed first as delimiter, so that the result of the SPLIT is
[ "", "9f0eb", "b0a3", "f037a0" ] instead of
[ "", ",", "9f0eb", "b0a3", "f037a0" ].
The empty string element caused by the first double quote mark in the bind parameter value, which would make the query return commit records with an empty string as hash, can be eliminated with REMOVE_VALUE.
The recommended way is to pass ["b0a3", "9f0eb", "f037a0"] as array however, as shown at the beginning.
like this:
with person FOR id in ["person/4201061993070840084011","person/1001230840198901011999","person/4201008406196506156918"]
FOR v,e,p In 1..1 ANY id
relation_samefamily,stay,relation_internetbar,relation_flight,relation_train
OPTIONS {
bfs:true
}
FILTER (p.edges[*]._from ALL IN ["person/42010619930708400840084011","person/10012310840989084001011999","person/4201060840196506156918"] and p.edges[*]._to ALL IN ["person/4201061993070808404011","person/1001231908408901011999","person/4200840106196506156918"])
RETURN {v,e}
Related
I have a Cosmos DB with documents that look like the following:
{
"name": {
"productName": "someProductName"
},
"identifiers": [
{
"identifierCode": "1234",
"identifierLabel": "someLabel1"
},
{
"identifierCode": "432",
"identifierLabel": "someLabel2"
}
]
}
I would like to write a sql query to obtain an entire document using "identifierLabel" as a filter when searching for the document.
I attempted to write a query based on an example I found from the following blog:
SELECT c,t AS identifiers
FROM c
JOIN t in c.identifiers
WHERE t.identifierLabel = "someLabel2"
However, when the result is returned, it appends the following to the end of the document:
{
"name": {
"productName": "someProductName"
},
"identifiers": [
{
"identifierCode": "1234",
"identifierLabel": "someLabel1"
},
{
"identifierCode": "432",
"identifierLabel": "someLabel2"
}
]
},
{
"identifierCode": "432",
"identifierLabel": "someLabel2"
}
How can I avoid this and get the result that I desire, i.e. the entire document with nothing appended to it?
Thanks in advance.
Using ARRAY_CONTAINS(), you should be able to do something like this to retrieve the entire document, without any need for a self-join:
SELECT *
FROM c
where ARRAY_CONTAINS(c.identifiers, {"identifierLabel":"someLabel2"}, true)
Note that ARRAY_CONTAINS() can search for either scalar values or objects. By specifying true as the third parameter, it signifies searching through objects. So, in the above query, it's searching all objects in the array where identifierLabel is set to "someLabel2" (and then it should be returning the original document, unchanged, avoiding the issue you ran into with the self-join).
I have a document that looks like:
{
_id: "....",
hostname: "mysite.com",
text: [
{
source: "this is text. this is some pattern",
...
},
{
source: "....",
...
}
]
}
and I am trying to delete the items from the text array which match a specific condition in my query given as:
db.getCollection('TM').updateMany(
{hostname: "mysite.com"},
{
$pull: {
"text.source": /this is some pattern/
}
},
{ multi: true }
)
Here I want to delete all the items from the array where the value inside source matches this is some pattern. When I execute this query, it gives an error saying: Cannot use the part (source) of (text.source) to traverse the element with error code 28.
What is the way to achieve this?
it gives an error saying: Cannot use the part (source) of (text.source) to traverse the element with error code 28.
Incorrect syntax of $pull for update methods,
Corrected syntax, and You can use $regex to find document by specific pattern,
"text.source" the condition in filter part will filter main documents, it is optional
text: { source: will filter sub documents and pull elements
db.getCollection('TM').updateMany(
{
hostname: "mysite.com",
"text.source": { $regex: "this is some pattern" }
},
{
$pull: {
text: { source: { $regex: "this is some pattern" } }
}
}
)
Playground
Is it possible to access an object's key inside the name portion of a .each?
let accounts =
[
{
details:
{
company_name:
"company_name",
email,
password:
"asdf",
},
find:
[
"_id",
"company_name",
"email",
"type",
],
type:
"creator"
},
{
details:
{
email,
first_name:
"first_name",
last_name:
"last_name",
password:
"asdf",
},
find:
[
"_id",
"email",
"first_name",
"last_name",
"type",
],
type:
"user"
},
]
describe.each(accounts)(
"%s", // <-- access the 'type' key, e.g. account.type
function (account)
{
// test code
}
)
Jest describe.each expects an array of arrays in the first parameter. If you pass in a 1D array, internally it will be mapped to an array of arrays (i.e. passing [1, 2, 3] as first parameter would be converted to [[1], [2], [3]]).
Each one of the arrays inside of the array is used as the data for a test suite. So, in the previous example, describe.each would generate three test suites, the first with 1 as data, the second with 2 as data and the third with 3 as data.
Now, in the test suite name, you can only format the parameters you are providing to it. In your case, you are passing to each test suite the data in each object of the accounts array. So, when you set the format specifiers in the test suite name, they will apply to the whole account object (i.e. the %s in your example will stringify your object resulting in [object Object]). Unfortunately, I don't think you can apply the format specifiers to a key of the object.
Some ideas to accomplish what you want:
Solution 1
If you use the %s formatter to compose the test suite name, the toString method of Object will be called (which by default returns [object Object]).
If you define a toString method in each of your accounts objects, that method will be used instead. So, we could add the toString method to each one of the account objects with this code (note that the toString method we are adding is returning the value for the type key):
const accounts = [{
details: {
company_name: "company_name",
email: "aa",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}, {
details: {
email: 'bb',
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
}].map(account => Object.assign(account, { toString: function() { return this.type; } }));
Now, with the %s format specifier you should see the account type in each test suite:
describe.each(accounts)(
"%s", // <-- This will cause the toString method to be called.
function (account)
{
// test code
}
)
Solution 2
You can always redefine each one of your test suite data so that the first parameter is the account type (note that now accounts is a 2D array):
let accounts = [
[
"creator",
{
details: {
company_name: "company_name",
email: "email",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}
], [
"user",
{
details: {
email: "email",
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
},
]
]
You can now use that first parameter (which is the account type) to give the test suite its name:
describe.each(accounts)(
'%s', // <-- This %s will format the first item in each test suite array.
function (accountType, account) {
// test code
}
);
Note that now your test function receives two parameters as each test suite array has two elements. The first one is the account type and the second one is the account data.
Solution 3
You can use the tagged template literal form of describe.each. With this solution you don't have to change your current definition of accounts array.
describe.each`
account
${accounts[0]}
${accounts[1]}
`('$account.type', function (account) {
// test code
});
The downside of this solution is that you have to manually append each test suite data in the template literal in a new line (i.e. if you add a new element to the accounts array you have to remember to add it in the template literal in a new line as ${accounts[2]}).
you can map your initial account array to convert each account into an array with 2 items:
the account type
the initial account element
Now, you can use the first element array in describe name
describe.each(accounts.map(account => [account.type, account]))(
'testing %s', // %s replaced by account type
(type, account) => { // note: 2 arguments now
it('details should be defined ', () => {
expect(account.details).toBeDefined();
});
},
);
As modern doc says, you can
generate unique test titles by injecting properties of test case object with $variable
So simply:
describe.each(accounts)(
"$type",
function (account) {
// tests
}
)
You can access nested object values like this: $variable.path.to.value
The same works on test.each level.
I had a similar problem with an object. I wanted to test an error message depending on http error codes, so I wrote a test object like so:
const expectedElements = {
error: {
code: 500,
title: "Problème avec l'API"
},
notFound:{
code: 404,
title: "Élement absent"
},
unauthorized:{
code: 401,
title: "Accès non autorisé"
}
};
I used Object.entries(obj) to get an array with those entries written like so: ['key','value']. I can access thoses as two parameters in the test. Here's how I wrote it:
test.each(Object.entries(expectedElements))("NoAccess show the right element for %s",(key,expectedElement)=>{
const { getByRole } = render(<NoAccess apiStatusCode={expectedElement.code}/>);
//test code
});
Now I can add cases as much as I want and I won't have to rewrite the test or create an array. I just write an new value in my expectedElements object. Bonus, I also have a descriptive test name!
Another alternative is to create a wrapper class and stick to a simple convention:
class TestCase {
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
toString() {
return JSON.stringify(this._value);
}
}
Then a test will look like this:
const testCases = accounts.map(TestCase)
describe.each(accounts)(
"%s", // <-- you can customize this in TestCase toString
function ({value: account})
{
// test code
}
)
I have a mongoose model in which some fields are like :
var AssociateSchema = new Schema({
personalInformation: {
familyName: { type: String },
givenName: { type: String }
}
})
I want to perform a '$regex' on the concatenation of familyName and givenName (something like 'familyName + " " + 'givenName'), for this purpose I'm using aggregate framework with $concat inside $project to produce a 'fullName' field and then '$regex' inside $match to search on that field. The code in mongoose for my query is:
Associate.aggregate([
{ $project: {fullName: { $concat: [
'personalInformation.givenName','personalInformation.familyName']}}},
$match: { fullName: { 'active': true, $regex: param, $options: 'i' } }}
])
But it's giving me error:
MongoError: $concat only supports strings, not double on the first
stage of my aggregate pipeline i.e $project stage.
Can anyone point out what I'm doing wrong ?
I also got this error and then discovered that indeed one of the documents in the collection was to blame. They way I fished it out was by filtering by field type as explained in the docs:
db.addressBook.find( { "zipCode" : { $type : "double" } } )
I found the field had the value NaN, which to my eyes wouldn't be a number, but mongodb interprets it as such.
Looking at your code, I'm not sure why $concat isn't working for you unless you've had some integers sneak into some of your document fields. Have you tried having a $-sign in front of your concatenated values? as in, '$personalInformation.givenName'? Are you sure every single familyName and givenName is a string, not a double, in your collection? All it takes is one double for your $concat to fold.
In any case, I had a similar type mismatch problem with actual doubles. $concat indeed supports only strings, and usually, all you'd do is cast any non-strings to strings.. but alas, at the time of this writing MongoDB 3.6.2 does not yet support integer/double => string casting, only date => string casting. Sad face.
That said, try adding this projection hack at the top of your query. This worked for me as a typecast. Just make sure you provide a long enough byte length (128-byte name is pretty long so you should be okay).
{
$project: {
castedGivenName: {
$substrBytes: [ 'personalInformation.givenName', 0, 128 ]
},
castedFamilyName: {
$substrBytes: [ 'personalInformation.familyName', 0, 128 ]
}
},
{
$project: {
fullName: {
$concat: [
'$castedGivenName',
'$castedFamilyName'
]
}
}
},
{
$match: { fullName: { 'active': true, $regex: param, $options: 'i' } }
}
I managed to make it work by using $substr method, so the $project part of my aggregate pipeline is now:
`$project: {
fullName: {
$concat: [
{ $substr: ['$personalInformation.givenName', 0, -1] }, ' ', { $substr: ['$personalInformation.familyName', 0, -1] }
]
}
}
}`
Is there a direct way to project a new field if a value matches one in a huge sub array. I know i can use the $elemMatch or $ in the $match condition, but that would not allow me to get the rest of the non matching values (users in my case).
Basically i want to list all type 1 items and show all the users while highlighting the subscribed user. The reason i want to do this through mongodb is to avoid iterating over multiple thousand users for every item. Infact that is the part 2 of my question, can i limit the number of user's array that would be returned, i just need around 10 array values to be returned not thousands.
The collection structure is
{
name: "Coke",
type: 2,
users:[{user: 13, type:1},{ user:2: type:2}]
},
{
name: "Adidas",
type: 1,
users:[{user:31, type:3},{user: 51, type:1}]
},
{
name: "Nike",
type: 1,
users:[{user:21, type:3},{user: 31, type:1}]
}
Total documents are 200,000+ and growing...
Every document has 10,000~50,000 users..
expected return
{
isUser: true,
name: "Adidas",
type: 1,
users:[{user:31, type:3},{user: 51, type:1}]
},
{
isUser: false,
name: "Nike",
type: 1,
users:[{user:21, type:3},{user: 31, type:1}]
}
and i've been trying this
.aggregate([
{$match:{type:1}},
{$project:
{
isUser:{$elemMatch:["users.user",51]},
users: 1,
type:1,
name: 1
}
}
])
this fails, i get an error "Maximum Stack size exceeded". Ive tried alot of combinations and none seem to work. I really want to avoid running multiple calls to mongodb. Can this be done in a single call?
I've been told to use unwind, but i am bit worried that it might lead to memory issues.
If i was using mysql, a simple subquery would have done the job... i hope i am overlooking a similar simple solution in mongodb.
Process the conditions for the array elements and match the result by using a combination of the $anyElementTrue which evaluates an array as a set and returns true if any of the elements are true and false otherwise, the $ifNull operator will act as a safety net that evaluates the following $map expression and returns the value of the expression if the expression evaluates to a non-null value. The $map in the $ifNull operator is meant to apply the conditional statement expression to each item in the users array and returns an array with the applied results. The resulting array will then be used evaluated by the $anyElementTrue and this will ultimately calculate and return the isUser field for each document:
db.collection.aggregate([
{ "$match": { "type": 1} },
{
"$project": {
"name": 1, "type": 1,
"isUser": {
"$anyElementTrue": [
{
'$ifNull': [
{
"$map": {
"input": "$users",
"as": "el",
"in": { "$eq": [ "$$el.user",51] }
}
},
[false]
]
}
]
}
}
}
])