Get all paths of matching jsonpath filter - postgresql

I'm using PostgreSQL 14.
I need to find all paths that matches the jsonpath filter I give.
Input example
{
"A": [
{
"B": [
[
{
"name": "id",
"conditions": [
{
"validator": "nonnull"
}
]
},
{
"name": "x",
"conditions": [
{
"validator": "required"
}
]
},
{
"name": "y",
"rules": []
}
],
[
{
"name": "z",
"conditions": [
{
"validator": "required"
}
]
}
]
]
}
]
}
JsonPath filter
Every A.B which has a required validator condition:
$.A.B[*].conditions ? (#.validator == "required")
Expected output (Or something close)
{A,0,B,0,1,conditions,0}
{A,0,B,1,0,conditions,0}

This is a relational solution, you may define the query as a view and filter on the validator and you get the array indexes.
select a.index-1 as a,
b.index-1 as b,
b2.index-1 as b2,
c.item ->> 'validator' as validator,
c.index-1 as conditions
from t cross join
jsonb_array_elements(jsn -> 'A') WITH ORDINALITY a(item, index) cross join
jsonb_array_elements(a.item -> 'B') WITH ORDINALITY b(item, index) cross join
jsonb_array_elements(b.item) WITH ORDINALITY b2(item, index) cross join
jsonb_array_elements(b2.item -> 'conditions') WITH ORDINALITY c(item, index);
a|b|b2|validator|conditions|
-+-+--+---------+----------+
0|0| 0|nonnull | 0|
0|0| 1|required | 0|
0|1| 0|required | 0|

Related

How can I filter records with multiple $in condition (some are optional $in) with arrays of string? Mongoose/MongoDB

You can see my Mongodb Records at last... I am now trying to implement search functionality,
I mad checkbox filtration for my project and below I listed those arrays after I clicked multiple checkboxes (see 1, 2 and 3).
I tried in aggregate with multiple match queries with $in, but it doesn't worked. Below arrays are used to check the records.
for example:
["Restaurant", "Mall"] need to check with "commercialType" in records, at the same time ["AC Rooms", "3 Phase Electricity"] need to check with "propertyFeatures.name" in records.. so all matching records must display if records exist with those filtrations.
I tried with multiple $in queries like this, but it gives empty records.
"$match": {
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
},
... other match filters
}
1. Below Array is used to find commercialType (field in doc)
[
'Restaurant',
'Office space',
'Hotel'
]
2. Below Array is used to find landType (field in doc)
[
'Bare land',
'Beachfront land',
'Coconut land'
]
3. Below Array is used to find "propertyFeatures.name" (field in doc)
[
'AC Rooms',
'3 Phase Electricity',
'Hot Water'
]
[
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "House",
"propertyFeatures": [
{
"id": 1,
"name": "AC Rooms",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Land",
"landType": "Bare land",
"propertyFeatures": [
{
"id": 1,
"name": "Wider Road",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Commercial",
"commercialType": "Restaurant",
"propertyFeatures": [
{
"id": 1,
"name": "3 Phase Electricity",
"value": true
}
]
}
]
You are probably missing $or operator, so your example pipeline becomes
[
{"$match": {
"$or": [
{
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
}
]
}
]
MongoDB docs: https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/#error-handling

MongoDB Inner Array Query

I have the following Mongo Document. I need the output for all SID =100 as shown. How can this be achieved. Tried different ways.
As seen, there are multiple array levels. The input has collection of SIDs with all products.
Input
[
{
"_id": "123456",
"Continent": {
"Country": [
[
"US",
{
"State": [
[
100,
{
"Product": "Corn",
"SID": 100
}
],
[
200,
{
"Product": "Maze",
"SID": 200
}
],
[
100,
{
"Product": "Corn-HB",
"SID": 100
}
]
],
}
]
]
}
}
]
Here the out has only the collection of SID = 100, but it preserves the input format
Output
[
{
"_id": "123456",
"Continent": {
"Country": [
[
"US",
{
"State": [
[
100,
{
"Product": "Corn",
"SID": 100
}
],
[
100,
{
"Product": "Corn-HB",
"SID": 100
}
]
],
}
]
]
}
}
]
As mentioned in the question comments, this data design is a bit odd but a solution can be achieved using the $function function (starting in v4.4 Sep 2020) which avoids maps of maps of reduce, etc.:
var keeper_sid = 100;
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: function(obj, keeper) {
var country_arr = obj['Continent']['Country'];
for(var n1 = 0; n1 < country_arr.length; n1++) {
var tuple1 = country_arr[n1];
var state_arr = tuple1[1]['State'];
// Walk the State array backwards to ease deletions.
for(var n2 = state_arr.length - 1; n2 >= 0; n2--) {
var tuple2 = state_arr[n2];
if(tuple2[1]['SID'] != keeper) {
state_arr.splice(n2,1);
}
}
}
return obj;
},
args: [ "$$ROOT", keeper_sid ],
lang: "js"
}}
}}
]);
This is straightforward but it does make assumptions about the structure e.g. extracting the second ([1]) item from the "tuples." The code can be reduced a bit more but intermediate variables (tuple1,2) are shown to make the whole thing a little more clear.

Sanity.io GROQ query for array of objects

I'm learning to code and now I am on the stage of a small pet project with Sanity as a CMS.
Long story short, making an API I'm trying to fetch cocktails data with votes for the cocktails. The votes are stored within persons who voted:
GROQ query
*[
_type == "cocktail" &&
!(_id in path('drafts.**'))
] {
name,
_id,
"votes" : *[_type == "person" && references(^._id)] {
votes[] {
score,
"id": cocktail._ref
}
}
}
which returns
[
{
"_id": "pdUGiuRzgLGpnc4cfx76nA",
"name": "Cuba Libre",
"votes": [
{
"votes": {
"id": "pdUGiuRzgLGpnc4cfx76nA",
"score": 2
}
},
{
"votes": {
"id": "pdUGiuRzgLGpnc4cfx76nA",
"score": 2
}
}
]
},
{
"_id": "pdUGiuRzgLGpnc4cfxBOyM",
"name": "The ERSH7",
"votes": []
}
]
As you can see, the merge provides embedded arrays of votes meanwhile I want sth like:
[{
...cocktail attributes...
"votes" : [
{score: 2, id: pdUGiuRzgLGpnc4cfx76nA},
{score: 2, id: pdUGiuRzgLGpnc4cfx76nA}
]
}
... more cocktails....
]
Trying to get this I modified the query:
*[
_type == "cocktail" &&
!(_id in path('drafts.**'))
] {
name,
_id,
"votes" : *[_type == "person" && references(^._id)].votes[] {
score,
"id": cocktail._ref
}
}
which should take a projection from every element of the votes arr. Unfortunately I get empty arrays:
[
{
"_id": "pdUGiuRzgLGpnc4cfx76nA",
"name": "Cuba Libre",
"votes": [
{},
{}
]
}
...more cocktails
]
How can I achieve the desired result?
Thank you for reading! Would appreciate any help!
Yes, had similar struggles with "flattening" the projections my self. I solved it with dot-syntax. Try just adding .votes on your first attempt:
*[
_type == "cocktail" &&
!(_id in path('drafts.**'))
]
{
name,
_id,
"votes" : *[_type == "person" && references(^._id)] {
votes[] {
score,
"id": cocktail._ref
}
}
.votes
}
If this is correct, the whole query can be simplified but I'm not at the level, yet, where I can do that without testing against a similar set ,'-)
Actually, GROQ has syntax to flatten array starting from v2021-03-25
find this example:
{
'nestedArray': [
{'foo': [1,2,3,4,5]},
{'foo': [6,7,8,9,10,11,12]},
{'foo': [13,14,15]},
]
}{
'stillNestedArray': #.nestedArray[].foo,
'flatArray': #.nestedArray[].foo[]
}
Note the [] right after foo - this is what's flatten it
So the original query from the question should look like
*[
_type == "cocktail" &&
!(_id in path('drafts.**'))
] {
name,
_id,
"votes" : *[_type == "person" && references(^._id)] {
votes[] {
score,
"id": cocktail._ref
}
}.votes[]
}

ElasticSearch - Get different types from different indices

I have two indices: A and B.
A has the following types: car, motorbike and van.
B has the following types: bus, car and pickup.
I want to be able to have a single query which gets motorbike and van from A and car and pickup from B.
I want to use a filter to do this and currently, I have:
.filter(
not(
should(
termsQuery("key", Seq("car", "bus"))
)
)
)
But obviously, this will filter car for both indices. I know I can do two separate queries for each index and filter different types for each but I want to avoid this if possible.
Is it possible to do what I am trying to do in a single query?
You can search on index and type by using the special fields _index and _type so once you know that, it's just a matter of putting together a boolean query.
search("_all").query(
boolQuery().should(
boolQuery().must(
termQuery("_index", "A"),
termsQuery("_type", "motorbike", "van")
),
boolQuery().must(
termQuery("_index", "B"),
termsQuery("_type", "car", "pickup")
)
)
)
You can do something like this.
GET _search
{
"query": {
"bool": {
"should": [
{
"bool": {
"filter": [
{
"term": {
"_index": {
"value": "A"
}
}
},
{
"terms": {
"_type": ["motorbike","van"]
}
}
]
}
},
{
"bool": {
"filter": [
{
"term": {
"_index": {
"value": "B"
}
}
},
{
"terms": {
"_type": ["car","pickup"]
}
}
]
}
}
]
}
}
}

mongodb check an element has one nested attribute

I have a collection of documents like this
[
{ "name": "pika", "attrs": { "A": 1, "B": 2 ... } },
{ "name": "chu", "attrs": { "C": 3 } },
{ "name": "plop", "attrs": { "A": 1, "C": 3 } }
]
I would like to delete records that have a "C" and only a "C" attribute in their "attrs" (line named "chu") using mongodb 2.4. The number of possible attributes under the attrs key is possibly large (> 100).
I can use several queries.
How would you do that ?
Edit : I want to keep attr C in lines containing other attributes.
You have two choices. If your key space is small you can do
db.collection.remove( {C : {$exists:true}, A: {$exists:false}, B: {$exists: false} })
Otherwise you'll need to do
var col = db.collection.find( {C : {$exists:true}} );
for(doc in col) {
var found = false
for(key in obj) {
if( key !== 'C' ) {
found = true;
break;
}
}
if(found === false) {
db.collection.remove(doc);
}
}
There's no way to count the number of keys in a document directly within MongoDB and there's no way to query on wildcards in key names (e.g. you can't do "key not equal to C : {$exists: false}"). So you either need to test all keys explicitly or test each document in your application layer.
If the "attrs" is a array, in other words, you collections like this:
{ "name": "pika", "attrs": [{ "A": 1}, {"B": 2}] };
{ "name": "chu", "attrs": [{ "C": 3 }] };
{ "name": "plop", "attrs": [{ "A": 1}, {"C": 3 }] }
Then you can write a query like below to find the specific record you want:
db.entities.find({"attrs.C": {$exists: true}, "attrs": {$size: 1}});
You can check the mongodb website to find the $size operation, http://docs.mongodb.org/manual/reference/operator/size/