Subquery for each item in a Couchbase double nested array - couchbase

Having the next objects
"a" : {
"id" : "1",
"arr" : [
{"id" : "b1"}, {"id" : "b2"}
]
}
"b1" : {
"id" : "b1",
"innerArr" : [{"id" : "c1"},{"id" : "c2"}]
}
"b2" : {
"id" : "b2",
"innerArr" : [{"id" : "c3"}]
}
"c1" : {
"name" : "c1"
}...
Right now I'm able to make a join with NEST over an array like this.
SELECT *
FROM bucket AS a
NEST bucket AS bs
ON META(a).id IN a.arr[*].id
{
"id" : "1",
"arr" : [
{"id" : "b1"}, {"id" : "b2"}
],
"bs" : [
{
"id" : "b1",
"innerArr" : [{"id" : "c1"},{"id" : "c2"}]
},
{
"id" : "b2",
"innerArr" : [{"id" : "c3"}]
}
]
}
Now I want to NEST c documents for each item in bs
Adding this NEST doesn't work
NEST bucket AS c
ON META(c).id IN bs[*].innerArr[*].id
I'm looking for this result:
{
"id" : "1",
"arr" : [
{"id" : "b1"}, {"id" : "b2"}
],
"bs" : [
{
"id" : "b1",
"innerArr" : [{"id" : "c1"},{"id" : "c2"}],
"cs" : [{"name" : "c1"},{"name" : "c2"}]
},
{
"id" : "b2",
"innerArr" : [{"id" : "c3"}],
"cs" : [{"name" : "c3"}]
}
]
}

I was able to solve it iterating every element from bs in a subquery. Since the subquery is in the SELECT part of the query, it must have USE KEYS instead of ON META().id =. Finally I add the subquery result to each item.
SELECT a*.
,ARRAY OBJECT_ADD(item, "cs", (SELECT c.* FROM bucket AS c USE KEYS item.innerArr[*].id)) FOR item IN bs END
FROM bucket AS a,
NEST bucket AS bs
ON META(a).id IN a.arr[*].id
It means that for each element in bs it queries every element of innerArray. And then adds the result to the element of bs.

NEST bucket AS bs .
bs starts with document (Scan, Fetch, ON). At the end of NEST bs becomes ARRAY for Filter, Group, projections, etc. Same applies chained JOIN, NEST. Example 17: https://blog.couchbase.com/ansi-join-support-n1ql/
In those situations use JOIN+GROUP on LEFT document + ARRAG_AGG on right document. Or use ARRAY …FOR… syntax.
The desired results can be achieved by following query. This LEFT outer nest
SELECT a.*,
(SELECT b.*,
(SELECT c.*
FROM bucket AS c USE KEYS b.innerArr[*].id) AS cs
FROM bucket AS b USE KEYS a.arr[*].id) AS bs
FROM bucket AS a
WHERE ..........;

Related

How to deeply remove an object from a Postgres JSONB array?

I'm using Postgres 10.12, and I have a table (reels_data) that has a jsonb column called blocks, which is an array of objects, each with its own type and data object. Example:
[
{
"type" : "LOGO",
"data" : {
"imageId" : 399
}
},
{
"type" : "CONTACT_INFO",
"data" : {
"email" : "",
"phone" : "",
"url" : "",
"name" : "Bob",
"jobTitle" : "Developer"
}
},
{
"type" : "MEDIA",
"data" : {
"playlists" : [
{
"id" : "134e3b49-fe08-43b9-b13a-dc886ec0af61",
"name" : "Untitled Playlist",
"media" : [
{
"id" : 265,
"fileUuid" : "8a7519b8-92dc-4978-a239-5b25d66caf45",
"itemType" : "TRACK",
"name" : "Test",
"duration" : "104.749"
},
{
"id" : 266,
"fileUuid" : "7409bbd5-f8a0-46f2-a077-78c14a4dcd80",
"itemType" : "TRACK",
"name" : "Test 2",
"duration" : "144.163"
},
{
"id" : 267,
"fileUuid" : "14c0d325-bfce-4ac5-a4f6-3edaa0e86ac5",
"itemType" : "TRACK",
"name" : "Test 3",
"duration" : "143.871"
}
]
}
]
}
}
]
My challenge is, if a user deletes media with ID 265, it has to be pulled from all the blocks of type "MEDIA", and to make it more complicated, from all of the playlists in the playlists array.
These blocks can be in any order, so I can't assume an index of 2. And there could be one playlist or 10, and the media to remove could exist in none or several of these playlists.
Is there a single Postgres query I could write to remove all media of ID x? Or is this better written as a SQL query to simply retrieve the above data, add some data processing in JavaScript, and then a build & commit a SQL transaction to update several rows with new data? Efficiency is the top priority (not taxing the DB server).
Considering your structure is fixed, please try below query:
with cte as(
select
id,
data->'type' "type",
data->'data' "data",
playlists->>'id',
playlists->>'name',
jsonb_build_object('id', playlists->>'id','name', playlists->>'name', 'media',json_agg(z.media) ) "playlists"
from reels_data t1
left join lateral jsonb_array_elements(t1.blocks) x(data) on true
left join lateral jsonb_array_elements(x.data->'data'->'playlists') y(playlists) on true
left join lateral jsonb_array_elements(y.playlists->'media') z(media) on true
where z.media->>'id' is null or z.media->>'id' <>'265'
group by 1,2,3,4,5
),
cte1 as
(
select id,jsonb_agg(final) "final_data" from (
select
id,
type,
data,
playlists,
jsonb_build_object('type',type,'data',case when type='"MEDIA"' then jsonb_build_object('playlists',jsonb_agg(playlists)) else data end) "final"
from
cte
group by 1,2,3,4) t1
group by 1
)
update reels_data t1 set blocks= t2."final_data" from cte1 t2 where t1.id=t2.id
It will replace all the objects with given id.
DEMO

How to push new key and value in JSON array in mongodb?

How can I push new key and value in JSON array?
I tried I used push keyword in update query but I got a different output. I used:
db.users.updateOne({"name":"viki"},{$push{"address.district":"thambaram"}})
I have this document:
{ "_id" : ObjectId("58934f10c7592b1494fd9a4d"), "name" : "viki", "age" : 100, "subject" : [ "c", "node.js", "java" ], "address" : { "city" : "chennai", "state" : "tamilnadu", "pincode" : "123" } }
I want to add "district":"thambaram" in address json array
I need like:
{ "_id" : ObjectId("58934f10c7592b1494fd9a4d"), "name" : "viki", "age" : 100, "subject" : [ "c", "node.js", "java" ], "address" : { "city" : "chennai", "state" : "tamilnadu", "pincode" : "123","district":"thambaram"} }
Use $set
db.users.updateOne({"name":"viki"},{$set:{"address.district":"thambaram"}})
This should work.
The $push operator appends a specified value to an array. In your case you should use $set

F# - Compare 2 JsonValue'

I have 2 Json values that are similar, but there are some differences.
Json1:
{
"id": "1",
"people" : [
{
"Id" : 1421,
"Name" : "Jackson",
"Age" : 21,
"Status" : "Available"
},
{
"Id" : 5916,
"Name" : "Steven",
"Age" : 22,
"Status" : "Available"
}
],
"totalRecords" : 2
}
Json2:
{
"id": "1",
"people" : [
{
"Id" : 1421,
"Name" : "Jackson",
"Age" : 21,
"Status" : "Available"
},
{
"Id" : 5916,
"Name" : "Steven",
"Age" : 22,
"Status" : "Unavailable"
},
{
"Id" : 1337,
"Name" : "Alice",
"Age" : 19,
"Status" : "Available"
}
],
"totalRecords" : 3
}
I'd like to know if there's a way to compare the two Jsonvalues. At the moment I de-serialize the data into a type and then use the Id's and the status' to see if anythings changed. I then pick out the parts that are different (In the example it'd be Steven and Alice) and add them to a sequence for later.
I'd like to reverse a few of the steps. I'd like too compare the json, find the differences, deserialize them and then add them to the sequence, or add them to the sequence then de-serialize the whole sequence. Either way, same result.
Any ideas?

How to update a nested array value in mongodb?

I want update a array value that is nested within an array value: i.e. set
status = enabled
where alerts.id = 2
{
"_id" : ObjectId("5496a8ed49847b6cd7c7b350"),
"name" : "joe",
"locations" : [
{
"name": "my location",
"alerts" : [
{
"id" : 1,
"status" : null
},
{
"id" : 2,
"status" : null
}
]
}
]
}
I would have used the position $ character, but cannot use it twice in a statement - multi positional operators are not supported yet: https://jira.mongodb.org/browse/SERVER-831
How do I issue a statement to only update the status field of an alert matching an id of 2?
UPDATE
If I change the schema as follows:
{
"_id" : ObjectId("5496ab2149847b6cd7c7b352"),
"name" : "joe",
"locations" : {
"my location" : {
"alerts" : [
{
"id" : 1,
"status" : "enabled"
},
{
"id" : 2,
"status" : "enabled"
}
]
},
"my other location" : {
"alerts" : [
{
"id" : 3,
"status" : null
},
{
"id" : 4,
"status" : null
}
]
}
}
}
I can then use:
update({"locations.my location.alerts.id":1},{$set: {"locations.my location.alerts.$.status": "enabled"}});
Problem is I cannot create indexes on the alert id :-(
it may be better of modelled as such, specially if an index on location and,or alerts.id is needed.
{
"_id" : ObjectId("5496a8ed49847b6cd7c7b350"),
"name" : "joe",
"location" : "myLocation",
"alerts" : [{
"id" : 1,
"status" : null
},
{
"id" : 2,
"status" : null
}
]
}
{
"_id" : ObjectId("5496a8ed49847b6cd7c7b350"),
"name" : "joe",
"location" : "otherLocation",
"alerts" : [{
"id" : 1,
"status" : null
},
{
"id" : 2,
"status" : null
}
]
}
I think you are having a wrong tool for the job. What you have in your example is relational data and it's much easier to handle with relational database. So I would suggest to use SQL-database instead of mongo.
But if you really want to do it with mongo, then I guess the only option is to fetch the document and modify it and put it back.

Cypher query JSON formatted result

On the Actor/Movie demo graph, cypher returns column names in a separate array.
MATCH (n:Person) RETURN n.name as Name, n.born as Born ORDER BY n.born LIMIT 5
results:
{ "columns" : [ "Name", "Born" ], "data" : [ [ "Max von Sydow", 1929 ], [ "Gene Hackman", 1930 ], [ "Richard Harris", 1930 ], [ "Clint Eastwood", 1930 ], [ "Mike Nichols", 1931 ] ]}
Is it possible to get each node properties tagged instead?
{ "nodes" : [ ["Name": "Max von Sydow", "Born": 1929 ], ...] }
If I return the node instead of selected properties, I get way too many properties.
MATCH (n:Person) RETURN n LIMIT 5
results:
{ "columns" : [ "n" ], "data" : [ [ { "outgoing_relationships" : "http://localhost:7474/db/data/node/58/relationships/out", "labels" : "http://localhost:7474/db/data/node/58/labels", "data" : { "born" : 1929, "name" : "Max von Sydow" }, "all_typed_relationships" : "http://localhost:7474/db/data/node/58/relationships/all/{-list|&|types}", "traverse" : "http://localhost:7474/db/data/node/58/traverse/{returnType}", "self" : "http://localhost:7474/db/data/node/58", "property" : "http://localhost:7474/db/data/node/58/properties/{key}", "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/58/relationships/out/{-list|&|types}", "properties" : "http://localhost:7474/db/data/node/58/properties", "incoming_relationships" : "http://localhost:7474/db/data/node/58/relationships/in", "extensions" : { }, "create_relationship" : "http://localhost:7474/db/data/node/58/relationships", "paged_traverse" : "http://localhost:7474/db/data/node/58/paged/traverse/{returnType}{?pageSize,leaseTime}", "all_relationships" : "http://localhost:7474/db/data/node/58/relationships/all", "incoming_typed_relationships" : "http://localhost:7474/db/data/node/58/relationships/in/{-list|&|types}" } ], ... ]}
You can use the new literal map syntax in Neo4j 2.0 and do something like:
MATCH (n:Person)
RETURN { Name: n.name , Born: n.born } as Person
ORDER BY n.born
LIMIT 5